private DataTreeObjectProperty[] MakeModelTransformPair(ConfigReference model, Transform3D transform, BooleanExpression expr = null) { string mdlName = model?.getName() ?? "null"; string trs = transform?.toString() ?? "null"; if (expr != null) { string state = "???"; try { state = expr.createEvaluator(new DummyScope()).evaluate().ToString(); } catch { } return(new DataTreeObjectProperty[] { new DataTreeObjectProperty("Condition: " + expr.toString() + " = " + state, SilkImage.Conditional), new DataTreeObjectProperty("Model: " + mdlName, SilkImage.Reference), new DataTreeObjectProperty("Transform: " + trs, SilkImage.Matrix) }); } else { return(new DataTreeObjectProperty[] { new DataTreeObjectProperty("Model: " + mdlName, SilkImage.Reference), new DataTreeObjectProperty("Transform: " + trs, SilkImage.Matrix) }); } }
public static (string[], string) GetDefaultTexturesAndActive(Imported model, string defFromVisibleMesh) { string[] textures = new string[model.materialMappings.Length]; SKAnimatorToolsProxy.IncrementEnd(textures.Length); for (int index = 0; index < model.materialMappings.Length; index++) { MaterialMapping mapping = model.materialMappings[index]; ConfigReference texRef = (ConfigReference)mapping.material.getArguments().getOrDefault("Texture", null); if (texRef != null) { string file = (string)texRef.getArguments().getOrDefault("File", null); if (file != null) { textures[index] = file; if (mapping.texture == defFromVisibleMesh) { defFromVisibleMesh = new FileInfo(file).Name; } } } SKAnimatorToolsProxy.IncrementProgress(); } return(textures, defFromVisibleMesh); }
/// <summary> /// Creates a new <see cref="ConfigReference"/> from the given name and <see cref="ArgumentMap"/><para/> /// For some reason, OOO doesn't offer a constructor to ConfigReference that takes in an <see cref="ArgumentMap"/>.<para/> /// ...And then they made all of the constructors that *can* populate args complete aids. I need to buy a punching bag. /// </summary> /// <param name="name"></param> /// <param name="args"></param> /// <returns></returns> public static ConfigReference NewConfigReference(string name, ArgumentMap args) { ConfigReference newCfg = new ConfigReference(name); newCfg.SetArguments(args); return(newCfg); }
#pragma warning disable CS0419 // Ambiguous reference in cref attribute /// <summary> /// A helper method for the two <see cref="Dereference"/> methods that takes in a Clyde object which is expected to be an animation of any type and resolves its refs. /// </summary> /// <param name="animationRef">A <see cref="ConfigReference"/> pointing to an animation.</param> /// <returns></returns> private static AnimationConfig.Implementation FinalDereference(ConfigReference animationRef) { object animationObj = animationRef.ResolveFile(); if (animationObj is AnimationConfig animation) { AnimationConfig.Implementation impl = animation.implementation; if (impl is AnimationConfig.Derived newDerived) { return(Dereference(newDerived)); } else { return(impl); } } else if (animationObj is AnimationConfig.ComponentAnimation newComponent) { return(Dereference(newComponent)); } else { XanLogger.WriteLine(string.Format(ERR_IMPL_NOT_SUPPORTED, animationObj.GetType().Name), color: Color.DarkGoldenrod); return(null); } }
/// <summary> /// Directly copied from SK jar for now. Sets the given key to the given value on a clone of this config reference, then returns the clone. /// </summary> /// <param name="cfgRef"></param> /// <param name="key"></param> /// <param name="value"></param> /// <returns></returns> public static ConfigReference SetArgumentOnClone(this ConfigReference cfgRef, string key, object value) { ConfigReference configReference; (configReference = (ConfigReference)cfgRef.clone()).getArguments().put(key, value); return(configReference); }
/// <summary> /// Attempts to selectively call <see cref="Resolve(ConfigReference)"/> or <see cref="ResolveFile(ConfigReference)"/>. /// </summary> /// <typeparam name="T"></typeparam> /// <param name="cfgRef"></param> /// <returns></returns> public static object ResolveAuto(this ConfigReference cfgRef) { if (cfgRef.IsFileReference()) { return(ResolveFile(cfgRef)); } else { return(Resolve(cfgRef)); } }
/// <summary> /// Sets the arguments of the given <see cref="ConfigReference"/>. /// </summary> /// <param name="cfgRef">The ConfigReference to alter.</param> /// <param name="newArgMap">The new ArgumentMap that this ConfigReference will use.</param> /// <exception cref="ArgumentNullException">If args is null</exception> public static void SetArguments(this ConfigReference cfgRef, ArgumentMap newArgMap) { if (newArgMap == null) { throw new ArgumentNullException("newArgMap"); } ArgumentMap currentArgs = cfgRef.getArguments(); currentArgs.clear(); object[] keys = newArgMap.keySet().toArray(); foreach (object key in keys) { currentArgs.put(key, newArgMap.get(key)); } }
/// <summary> /// Attempts to selectively call <see cref="Resolve{T}(ConfigReference)"/> or <see cref="ResolveFile{T}(ConfigReference)"/>.<para/> /// Unlike <see cref="ResolveAuto(ConfigReference)"/>, this may throw a <see cref="InvalidCastException"/> in the event that the config is not a file reference and <typeparamref name="T"/> is not a <see cref="ManagedConfig"/>. /// </summary> /// <typeparam name="T"></typeparam> /// <param name="cfgRef"></param> /// <returns></returns> public static T ResolveAuto <T>(this ConfigReference cfgRef) where T : class { if (cfgRef.IsFileReference()) { return(ResolveFile <T>(cfgRef)); } else { if (typeof(ManagedConfig).IsAssignableFrom(typeof(T))) { return(Resolve <ManagedConfig>(cfgRef) as T); } else { throw new InvalidCastException("ResolveAuto<T> for a ConfigReference failed because the reference points to configs and the desired type is not a ManagedConfig!"); } } }
/// <summary> /// Takes in a <see cref="ConfigReference"/> and loads its data. It then returns the loaded model and all of its descendants as a list of <see cref="Model3D"/> instances. /// </summary> /// <param name="sourceFile">The original base-level file that contains the reference.</param> /// <param name="reference">The reference itself.</param> /// <param name="modelCollection">A list of every model that has been loaded recursively.</param> /// <param name="dataTreeParent">For cases where the GUI is used, this is the data tree representation.</param> /// <param name="globalTransform">The transformation to apply to all loaded models.</param> /// <param name="appendModelsToModelCollection">If true, the loaded models will be appended to <paramref name="modelCollection"/>.</param> /// <param name="extraData">Any extra data that should be included. This is mainly used by references (e.g. a reference is a <see cref="StaticSetConfig"/>, the target model in the set may be included as extra data)</param> public static List <Model3D> HandleConfigReference(FileInfo sourceFile, ConfigReference reference, List <Model3D> modelCollection, DataTreeObject dataTreeParent, Transform3D globalTransform, bool appendModelsToModelCollection = true, Dictionary <string, dynamic> extraData = null) { if (reference == null) { return(null); } string filePathRelativeToRsrc = reference.getName(); if (extraData == null) { extraData = reference.ArgumentsToExtraData(); } else { extraData = extraData.MergeWith(reference.ArgumentsToExtraData()); } return(HandleConfigReferenceFromLiteralPath(sourceFile, filePathRelativeToRsrc, modelCollection, dataTreeParent, globalTransform, appendModelsToModelCollection, extraData)); }
/// <summary> /// Using the information in this <see cref="ConfigReference"/>, the object this reference points to will be resolved and returned. This <see cref="ConfigReference"/> must point to an actual configuration. If it points to a file, an <see cref="InvalidOperationException"/> will be thrown.<para/> /// This will automatically populate the arguments in the referenced config if applicable, making usage of the returned object relatively straightforward. /// </summary> /// <param name="cfgRef">The ConfigReference to resolve the reference to.</param> /// <typeparam name="T">The destination type for the new ManagedConfig</typeparam> /// <returns>The ManagedConfig the given ConfigReference is pointing to.</returns> /// <exception cref="InvalidOperationException">If IsFileReference() returns true on this ConfigReference.</exception> public static T Resolve <T>(this ConfigReference cfgRef) where T : ManagedConfig { if (cfgRef.IsFileReference()) { throw new InvalidOperationException("Cannot resolve the path to a non-config reference (this ConfigReference points to a file!)"); } T mgCfg = ConfigReferenceBootstrapper.ConfigReferences.TryGetReferenceFromName(cfgRef.getName())?.CloneAs <T>(); // Populate the values of the referenced object if possible. if (mgCfg is ParameterizedConfig paramCfg) { // This can store arguments. paramCfg.ApplyArguments(cfgRef.getArguments()); return(paramCfg as T); } // It's gotta stay as a ManagedConfig // Can't make use of this to apply arguments to. How do I handle this? // For now: Return the object without any changes. return(mgCfg); }
/// <summary> /// Attempts to resolve this <see cref="ConfigReference"/>, which is expected to point to a file, and returns the Clyde object that it points to.<para/> /// This is intended for use in cases where data must absolutely be loaded in-line. Most cases are better suited for <see cref="ConfigReferenceUtil"/> and its methods. /// </summary> /// <typeparam name="T"></typeparam> /// <param name="cfgRef"></param> /// <returns></returns> public static T ResolveFile <T>(this ConfigReference cfgRef) where T : class { if (!cfgRef.IsFileReference()) { throw new InvalidOperationException("Cannot resolve this ConfigReference as a file because the file it points to does not exist (or it references an actual config object)!"); } object clydeObject = ClydeFileHandler.GetRaw(new FileInfo(ResourceDirectoryGrabber.ResourceDirectoryPath + cfgRef.getName())); if (clydeObject == null) { throw new NullReferenceException("Failed to load Clyde file!"); } // Apply any arguments. if (clydeObject is ParameterizedConfig paramCfg) { paramCfg.ApplyArguments(cfgRef.getArguments() ?? new ArgumentMap()); } return(clydeObject as T); }
/// <summary> /// Takes the <see cref="ArgumentMap"/> within this <see cref="ConfigReference"/> and translates it into the dictionary format used by TRS's extra data containers. /// </summary> /// <param name="cfgRef">The ConfigReference to get the data from.</param> /// <param name="createSubDict">If true, the returned dictionary has a single key called "DirectArgs" which contains the arguments. If false, the arguments are returned as the dictionary itself.</param> /// <returns>A dictionary containing the arguments for the directs on the given destination object.</returns> public static Dictionary <string, dynamic> ArgumentsToExtraData(this ConfigReference cfgRef, bool createSubDict = true) { Dictionary <string, dynamic> dict = new Dictionary <string, dynamic>(); ArgumentMap args = cfgRef.getArguments(); object[] keys = args.keySet().toArray(); foreach (object key in keys) { dict[key.ToString()] = args.get(key); } if (createSubDict) { return(new Dictionary <string, dynamic> { ["DirectArgs"] = dict }); } else { return(dict); } }
/// <summary> /// Given a <see cref="ParameterizedConfig"/> and a path from a direct as well as a target value, this will modify the <see cref="ParameterizedConfig"/> so that its fields reflect the given value.<para/> /// If this data cannot be set due to it being on a direct chain (the end value is a <see cref="ConfigReference"/>), that <see cref="ConfigReference"/> will be returned /// </summary> /// <param name="config"></param> /// <param name="path"></param> /// <param name="argValue"></param> /// <param name="setToNull">If true, and if argValue is null, the property will actually be set to null (if this is false, it will skip applying it)</param> public static ConfigReference SetDataOn(ParameterizedConfig config, string path, object argValue, bool setToNull = false) { if (argValue == null && !setToNull) { return(null); } // implementation.material_mappings[0].material["Texture"]["File"] // A bit of a hack to make splitting this path easier: path = path.Replace("[", ".["); // The latest object stored when traversing this direct's path. object latestObject = config; string previousIndex = null; object previousObject = null; // Split it by the segments of this path, and get rid of the implementation word at the start if needed. string[] pathSegments = path.Split('.'); if (pathSegments[0] == "implementation") { latestObject = ReflectionHelper.Get(latestObject, "implementation"); pathSegments = pathSegments.Skip(1).ToArray(); } for (int idx = 0; idx < pathSegments.Length; idx++) { string currentIndex = pathSegments[idx].SnakeToCamel(); string betweenBrackets = currentIndex.BetweenBrackets(); if (betweenBrackets != null) { // This is either an array index, or a reference to a config. // The simple way to test this is that if it's a numeric index, it's an array index. if (int.TryParse(betweenBrackets, out int arrayIndex)) { // Access this array index. It is a number in brackets like [0] previousObject = latestObject; latestObject = ReflectionHelper.GetArray(latestObject, arrayIndex); } else { // Access the config reference. This is branching from a config reference and accesses a parameter ["Parameter Name"] ConfigReference latestAsCfg = (ConfigReference)latestObject; string parameterName = betweenBrackets.Substring(1, betweenBrackets.Length - 2); // First things first: Resolve the config reference. string configRefPath = latestAsCfg.getName(); // cloning this is super important as the tryget method will return a template object. // Do not edit the template! if (!latestAsCfg.IsRealReference()) { // Catch case: This isn't actually pointing to a *configuration*, rather a direct object reference. latestAsCfg.getArguments().put(parameterName, argValue); return(null); } ParameterizedConfig referencedConfig = ConfigReferenceBootstrapper.ConfigReferences.TryGetReferenceFromName(configRefPath)?.CloneAs <ParameterizedConfig>(); if (referencedConfig == null) { XanLogger.WriteLine("Something failed to reference a ConfigReference (It tried to search for \"" + configRefPath + "\", which doesn't exist). Some information on this model may not load properly!", XanLogger.DEBUG, System.Drawing.Color.DarkGoldenrod); return(null); } // So there's our reference. Now we need to get a parameter from it. Parameter referencedParam = referencedConfig.getParameter(parameterName); if (referencedParam is Parameter.Direct referencedDirect) { ConfigReference[] chainRefs = SetDataOn(referencedConfig, referencedDirect.paths, argValue); if (chainRefs != null) { // We're pointing to other ConfigReferences which means that this is a direct chain. Oh brother. foreach (ConfigReference reference in chainRefs) { if (reference != null) { if (File.Exists(ResourceDirectoryGrabber.ResourceDirectoryPath + configRefPath)) { // Catch case: This isn't actually pointing to a *configuration*, rather a direct object reference. latestAsCfg.getArguments().put(parameterName, argValue); } else { ParameterizedConfig forwardRefConfig = reference.Resolve <ParameterizedConfig>(); // Using as because it might be null. if (forwardRefConfig != null) { foreach (Parameter subRefParam in forwardRefConfig.parameters) { if (subRefParam is Parameter.Direct subRefDirect) { WrappedDirect wrappedSubRefDir = new WrappedDirect(forwardRefConfig, subRefDirect); wrappedSubRefDir.SetValue(argValue); } } latestAsCfg.getArguments().put(parameterName, ConfigReferenceConstructor.MakeConfigReferenceTo(forwardRefConfig)); } else { XanLogger.WriteLine("ALERT: Model attempted to set value of Direct [" + currentIndex + "] but it failed because the target object was not a ParameterizedConfig! Some information may be incorrect on this model.", XanLogger.DEBUG, System.Drawing.Color.DarkGoldenrod); return(null); } } } } } else { // This is by far one of the most hacky methods I've ever done in OOO stuff. // So basically, a model has a property for something like say, materials. // This property is a ConfigReference to a material object, and that ConfigReference has arguments in it // that tell the referenced material what it should be. // Rather than trying to traverse that ConfigReference and set the data on the remote object (PAINFUL), I // instead decided to write a system that can wrap any ParameterizedConfig into a ConfigReference and just // call it a day. ReflectionHelper.Set(previousObject, previousIndex, ConfigReferenceConstructor.MakeConfigReferenceTo(referencedConfig)); } } else { //throw new NotImplementedException("Cannot set data on referenced parameters that are not Directs (yet)."); XanLogger.WriteLine("Feature Not Implemented: Cannot set data on referenced parameters that aren't directs (e.g. parameters that are choices)", XanLogger.STANDARD, System.Drawing.Color.Orange); return(null); } return(null); } } else { // This is referencing a property. // But wait: If this is the second to last object, then we gotta modify it. if (idx == pathSegments.Length - 1) { // Second to last object. latestObject will contain the property that we want to set. // Let's manually find that field and set it if (currentIndex.BetweenBrackets() == null) { // We're good here. if (argValue is ConfigReference argValueCfg) { // There's some cases when a variant wants to set a config reference. // In these cases, we need to make sure the property is also a config reference so we know it's safe to set. // ... But before that, catch case: Not actually a config. if (argValueCfg.IsRealReference()) { object ptr = ReflectionHelper.Get(previousObject, previousIndex); if (ptr is ConfigReference) { ReflectionHelper.Set(previousObject, previousIndex, argValueCfg); } else { if (ReflectionHelper.GetTypeOfField(latestObject, currentIndex) == argValueCfg.GetType()) { ReflectionHelper.Set(latestObject, currentIndex, argValueCfg); return(null); } else { XanLogger.WriteLine("ALERT: Model attempted to set value of Direct [" + currentIndex + "] but it failed due to a type mismatch! Certain data on this model might be incorrect.", XanLogger.DEBUG, System.Drawing.Color.DarkGoldenrod); return(null); } } } else { if (ReflectionHelper.GetTypeOfField(latestObject, currentIndex) == argValueCfg.GetType()) { ReflectionHelper.Set(latestObject, currentIndex, argValueCfg); return(null); } else { XanLogger.WriteLine("ALERT: Model attempted to set value of Direct [" + currentIndex + "] but it failed due to a type mismatch! Certain data on this model might be incorrect.", XanLogger.DEBUG, System.Drawing.Color.DarkGoldenrod); return(null); } } } else { // Contrary to the oddball case above, if the result value at the end of this direct is a ConfigReference // then we need to return it so that whatever called this knows that it has more to traverse. // Ideally, this is only returned in the nested call above. string targetIndex = previousIndex; /*if (int.TryParse(previousIndex.BetweenBrackets(), out int _)) { * // The previous index was an array accessor. This means we want to actually reference the CURRENT index * // on the PREVIOUS object. A bit odd but it's intentional. * // This is done because the previous object will be the result of that indexer, which is identical * // to the current object. As such, we need to use the current index to reference it. * targetIndex = currentIndex; * }*/ if (previousObject == null || targetIndex == null) { return(null); } object ptr = ReflectionHelper.Get(previousObject, targetIndex); if (ptr is ConfigReference cfgRef) { // Special handling. argValue goes to a property on the config reference return(cfgRef); } if (ptr.GetType() == argValue.GetType()) { ReflectionHelper.Set(previousObject, targetIndex, argValue); } else { // In some cases, the object it's pointing to isn't the same time. // In cases where the previous index is used, this *might* mean we need to step forward like so: if (ReflectionHelper.GetTypeOfField(ptr, currentIndex) == argValue.GetType()) { ReflectionHelper.Set(ptr, currentIndex, argValue); } else { // But in other cases, it's not pointing to a prop up ahead. if (ReflectionHelper.Get(ptr, currentIndex) is ConfigReference cfgRefLow) { return(cfgRefLow); } else { XanLogger.WriteLine("ALERT: Model attempted to set value of Direct [" + currentIndex + "] but it failed due to a type mismatch! Certain data on this model might be incorrect.", XanLogger.DEBUG, System.Drawing.Color.DarkGoldenrod); return(null); } } } return(null); } } } if (previousIndex != null) { if (int.TryParse(previousIndex.BetweenBrackets(), out int _) && idx == pathSegments.Length - 1) { if (currentIndex.BetweenBrackets() == null) { // We're good here. ReflectionHelper.Set(previousObject, previousIndex, argValue); return(null); } } } previousObject = latestObject; latestObject = ReflectionHelper.Get(latestObject, currentIndex); if (previousObject == null || latestObject == null) { return(null); // Failed to traverse. } } previousIndex = currentIndex; } return(null); }
/// <summary> /// Given a full path from a <see cref="Parameter.Direct"/>, this will traverse it and acquire the data at the end.<para/> /// This will stop if it runs into another direct and instantiate a new <see cref="WrappedDirect"/>. This will occur if there is a reference chain, for instance, in many textures it references material["Texture"] (a direct) followed by a second direct ["File"]. Since each may have multiple paths, it's best to reference a new <see cref="WrappedDirect"/>. /// </summary> /// <param name="path"></param> private void TraverseDirectPath(string path) { // implementation.material_mappings[0].material["Texture"]["File"] // A bit of a hack to make splitting this path easier: path = path.Replace("[", ".["); // The latest object stored when traversing this direct's path. object latestObject = Config; // Split it by the segments of this path, and get rid of the implementation word at the start if needed. string[] pathSegments = path.Split('.'); if (pathSegments[0] == "implementation") { latestObject = ReflectionHelper.Get(latestObject, "implementation"); pathSegments = pathSegments.Skip(1).ToArray(); } for (int idx = 0; idx < pathSegments.Length; idx++) { string currentIndex = pathSegments[idx].SnakeToCamel(); string betweenBrackets = currentIndex.BetweenBrackets(); if (betweenBrackets != null) { // This is either an array index, or a reference to a config. // The simple way to test this is that if it's a numeric index, it's an array index. if (int.TryParse(betweenBrackets, out int arrayIndex)) { // Access this array index. It is a number in brackets like [0] latestObject = ReflectionHelper.GetArray(latestObject, arrayIndex); } else { // Access the config reference. This is branching from a config reference and accesses a parameter ["Parameter Name"] ConfigReference latestAsCfg = (ConfigReference)latestObject; string parameterName = betweenBrackets.Substring(1, betweenBrackets.Length - 2); // First things first: Resolve the config reference (AND CLONE IT. Don't edit the template object!) string configRefPath = latestAsCfg.getName(); if (!latestAsCfg.IsRealReference()) { // Catch case: This isn't actually pointing to a *configuration*, rather a direct object reference. _EndReferences.Add(new DirectEndReference(configRefPath)); return; } ParameterizedConfig referencedConfig = ConfigReferenceBootstrapper.ConfigReferences.TryGetReferenceFromName(configRefPath)?.CloneAs <ParameterizedConfig>(); if (referencedConfig == null) { XanLogger.WriteLine("Something failed to reference a ConfigReference (It tried to search for \"" + configRefPath + "\", which doesn't exist). Some information on this model may not load properly!", XanLogger.DEBUG, System.Drawing.Color.DarkGoldenrod); return; } ArgumentMap args = latestAsCfg.getArguments(); // So there's our reference. Now we need to get a parameter from it. ConfigReferenceContainerName = ConfigReferenceBootstrapper.ConfigReferences.GetCategoryFromEntryName(configRefPath); Parameter referencedParam = referencedConfig.getParameter(parameterName); if (referencedParam is Parameter.Direct referencedDirect) { _EndReferences.Add(new DirectEndReference(new WrappedDirect(referencedConfig, referencedDirect, null, args))); } else if (referencedParam is Parameter.Choice referencedChoice) { _EndReferences.Add(new DirectEndReference(new WrappedChoice(referencedConfig, referencedChoice, args))); } return; } } else { // This is referencing a property. latestObject = ReflectionHelper.Get(latestObject, currentIndex); } } // Now here's something important: Does an argument override this? if (Arguments != null && Arguments.containsKey(Name) && latestObject != null) { // This direct is included as an argument... // And if we're down here, then we're not referencing another direct, we're referencing a property. // But as a final sanity check: if (latestObject.GetType() == Arguments.get(Name)?.GetType()) { // Yep! Our argument is the same type as the latestObject. // Let's set latestObject to that argument. latestObject = Arguments.get(Name); } } _EndReferences.Add(new DirectEndReference(latestObject)); }
/// <summary> /// Returns <see langword="true"/> if this <see cref="ConfigReference"/> points to an actual config, and <see langword="false"/> if it does not (for instance, it points to a model file instead). /// </summary> /// <param name="cfgRef"></param> /// <returns>False if this ConfigReference points to a literal file (points to a .dat file in the rsrc directory), and true if it points to an actual configuration object (e.g. Texture/2D/Default for textures)</returns> public static bool IsRealReference(this ConfigReference cfgRef) { return(!File.Exists(ResourceDirectoryGrabber.ResourceDirectoryPath + cfgRef.getName())); }
/// <summary> /// Returns the currently active textures for each of the <see cref="MaterialMapping"/>s within this <see cref="ModelConfig"/>.<para/> /// This will only pull the textures actively in use by the model. Any other variants will not be acquired. /// </summary> /// <param name="model"></param> /// <returns></returns> public static string[] GetDefaultTextures(Imported model) { string[] textures = new string[model.materialMappings.Length]; SKAnimatorToolsProxy.IncrementEnd(textures.Length); for (int index = 0; index < model.materialMappings.Length; index++) { MaterialMapping mapping = model.materialMappings[index]; ConfigReference texRef = (ConfigReference)mapping.material.getArguments().getOrDefault("Texture", null); if (texRef != null) { string file = (string)texRef.getArguments().getOrDefault("File", null); if (file != null) { textures[index] = file; } } SKAnimatorToolsProxy.IncrementProgress(); } /* * for (int index = 0; index < model.materialMappings.Length; index++) { * MaterialMapping mapping = model.materialMappings[index]; * MaterialConfig material = mapping.material.ResolveAuto<MaterialConfig>(); * if (material != null) { * while (material.implementation is MaterialConfig.Derived derivedMtl) { * material = derivedMtl.material.ResolveAuto<MaterialConfig>(); * } * * if (material.implementation is MaterialConfig.Original originalMtl) { * foreach (TechniqueConfig technique in originalMtl.techniques) { * TechniqueConfig.Enqueuer enqueuer = technique.enqueuer; * if (enqueuer is TechniqueConfig.NormalEnqueuer normalEnq) { * foreach (PassConfig pass in normalEnq.passes) { * // Just find the first pass with a texture. It's not possible to have multiple textures right now in gltf * // that is, on one model * if (pass.textureState != null) { * TextureStateConfig texState = pass.textureState; * if (texState.units.Length > 0) { * TextureConfig texture = texState.units[0].texture.ResolveAuto<TextureConfig>(); * while (texture.implementation is TextureConfig.Derived derivedTexture) { * texture = derivedTexture.texture.ResolveAuto<TextureConfig>(); * } * * if (texture.implementation is TextureConfig.Original2D tex2d) { * TextureConfig.Original2D.Contents contents = tex2d.contents; * if (contents is TextureConfig.Original2D.ImageFile imageFile) { * textures[index] = imageFile.file; * } else { * // Blank. * textures[index] = null; * } * } else { * XanLogger.WriteLine("Unsupported texture type " + texture.GetType().Name); * } * } * } * } * } * } * } * } * * SKAnimatorToolsProxy.IncrementProgress(); * } */ return(textures); }
/// <summary> /// Returns <see langword="true"/> if this <see cref="ConfigReference"/> points to a .dat file, and <see langword="false"/> if it does not (for instance, it's being used as an actual reference to a config object). /// </summary> /// <param name="cfgRef"></param> /// <returns>True if this ConfigReference points to a literal file (points to a .dat file in the rsrc directory), and false if it points to an actual configuration object (e.g. Texture/2D/Default for textures)</returns> public static bool IsFileReference(this ConfigReference cfgRef) => !IsRealReference(cfgRef);
public void HandleModelConfig(FileInfo sourceFile, ModelConfig baseModel, List <Model3D> modelCollection, DataTreeObject dataTreeParent = null, Transform3D globalTransform = null, Dictionary <string, dynamic> extraData = null) { // ModelConfigHandler.SetupCosmeticInformation(baseModel, dataTreeParent); // ArticulatedConfig has a lot of steps. SKAnimatorToolsProxy.IncrementEnd(4); ArticulatedConfig model = (ArticulatedConfig)baseModel.implementation; SetupCosmeticInformation(model, dataTreeParent); MeshSet meshes = model.skin; VisibleMesh[] renderedMeshes = meshes.visible; Dictionary <string, Armature> allInstantiatedArmatures = new Dictionary <string, Armature>(); List <Model3D> allModelsAndNodes = new List <Model3D>(); // 1 SKAnimatorToolsProxy.IncrementProgress(); int idx = 0; string depth1Name = ResourceDirectoryGrabber.GetDirectoryDepth(sourceFile); string fullDepthName = ResourceDirectoryGrabber.GetDirectoryDepth(sourceFile, -1); SKAnimatorToolsProxy.IncrementEnd(renderedMeshes.Length); foreach (VisibleMesh mesh in renderedMeshes) { string meshTitle = "-Skin-Mesh[" + idx + "]"; Model3D meshToModel = GeometryConfigTranslator.GetGeometryInformation(mesh.geometry, fullDepthName + meshTitle, model.root); meshToModel.Name = depth1Name + meshTitle; if (globalTransform != null) { meshToModel.Transform.composeLocal(globalTransform); } (List <string> textureFiles, string active) = ModelPropertyUtility.FindTexturesAndActiveFromDirects(baseModel, mesh.texture); meshToModel.Textures.SetFrom(textureFiles); meshToModel.ActiveTexture = active; if (meshToModel.Mesh.HasBoneData) { XanLogger.WriteLine("Model has bone data, setting that up.", XanLogger.TRACE); // meshToModel.Mesh.SetBones(model.root); // ^ now called by GetGeometryInformation foreach (KeyValuePair <string, Armature> boneNamesToBones in meshToModel.Mesh.AllBones) { allInstantiatedArmatures[boneNamesToBones.Key] = boneNamesToBones.Value; } allModelsAndNodes.Add(meshToModel); } modelCollection.Add(meshToModel); idx++; SKAnimatorToolsProxy.IncrementProgress(); } // 2 SKAnimatorToolsProxy.IncrementProgress(); SKAnimatorToolsProxy.IncrementEnd(GetNodeCount(model.root)); Dictionary <string, Model3D> nodeModels = new Dictionary <string, Model3D>(); RecursivelyIterateNodes(baseModel, model, sourceFile, model.root, modelCollection, globalTransform, globalTransform, nodeModels, fullDepthName); allModelsAndNodes.AddRange(nodeModels.Values); SKAnimatorToolsProxy.SetProgressState(ProgressBarState.ExtraWork); SKAnimatorToolsProxy.IncrementEnd(model.attachments.Length); foreach (Attachment attachment in model.attachments) { List <Model3D> attachmentModels = ConfigReferenceUtil.HandleConfigReference(sourceFile, attachment.model, modelCollection, dataTreeParent, globalTransform); if (attachmentModels == null) { SKAnimatorToolsProxy.IncrementProgress(); continue; // A lot of attachments have null models and I'm not sure why. } // NEW BEHAVIOR: Is the model root-less but rigged? // Set its root to *this* model foreach (Model3D mdl in attachmentModels) { if (mdl.Mesh != null && mdl.Mesh.UsesExternalRoot) { mdl.Mesh.SetBones(model.root); } } SKAnimatorToolsProxy.IncrementEnd(attachmentModels.Count); foreach (Model3D referencedModel in attachmentModels) { referencedModel.Transform.composeLocal(attachment.transform); if (allInstantiatedArmatures.ContainsKey(attachment.node ?? string.Empty)) { referencedModel.AttachmentNode = allInstantiatedArmatures[attachment.node]; XanLogger.WriteLine("Attached [" + referencedModel.Name + "] to [" + attachment.node + "]", XanLogger.TRACE); } else { // New catch case: This might actually be the name of a model! if (nodeModels.ContainsKey(attachment.node ?? string.Empty)) { // Indeed it is! referencedModel.AttachmentModel = nodeModels[attachment.node]; referencedModel.AttachmentModel.Transform.setScale(1f); // TODO: Is this okay? if (referencedModel.Transform.getType() < Transform3D.AFFINE) { float scale = referencedModel.Transform.getScale(); referencedModel.Transform.set(new Transform3D(new Vector3f(), Quaternion.IDENTITY, scale)); } else { Vector3f scale = referencedModel.Transform.extractScale(); referencedModel.Transform.set(new Transform3D(new Vector3f(), Quaternion.IDENTITY, scale)); } XanLogger.WriteLine("Attached [" + referencedModel.Name + "] to [" + attachment.node + "]", XanLogger.TRACE); } else { XanLogger.WriteLine("Attachment wanted to attach to node or model [" + attachment.node + "] but it does not exist!"); } } SKAnimatorToolsProxy.IncrementProgress(); } SKAnimatorToolsProxy.IncrementProgress(); } SKAnimatorToolsProxy.SetProgressState(ProgressBarState.OK); // 3 SKAnimatorToolsProxy.IncrementProgress(); SKAnimatorToolsProxy.IncrementEnd(model.animationMappings.Length); foreach (AnimationMapping animationMapping in model.animationMappings) { ConfigReference animationRef = animationMapping.animation; if (animationRef.IsFileReference()) { object animationObj = animationRef.ResolveFile(); if (animationObj is AnimationConfig animation) { SKAnimatorToolsProxy.SetProgressState(ProgressBarState.ExtraWork); AnimationConfigHandler.HandleAnimationImplementation(animationRef, animationMapping.name, animation, animation.implementation, allModelsAndNodes); SKAnimatorToolsProxy.SetProgressState(ProgressBarState.OK); } } SKAnimatorToolsProxy.IncrementProgress(); } // 4 SKAnimatorToolsProxy.IncrementProgress(); }
// private const string ERR_PROC_NOT_YET_SUPPORTED = "This Procedural animation [{0}] cannot be loaded yet! Only certain instances of this type work right now."; /// <summary> /// Handles the given <see cref="AnimationConfig"/> and applies it to the already loaded <see cref="Model3D"/>s. /// </summary> /// <param name="srcConfig"></param> /// <param name="name"></param> /// <param name="original"></param> /// <param name="animationImplementation"></param> /// <param name="attachToModels"></param> public static void HandleAnimationImplementation(ConfigReference srcConfig, string name, AnimationConfig original, AnimationConfig.Implementation animationImplementation, List <Model3D> attachToModels) { // FileInfo srcFile = new FileInfo(ResourceDirectoryGrabber.ResourceDirectoryPath + srcConfig.getName()); SKAnimatorToolsProxy.IncrementEnd(); // Clear out any derived references all the way until we dig down to an original implementation. if (animationImplementation is AnimationConfig.Derived derived) { animationImplementation = Dereference(derived); } if (animationImplementation is AnimationConfig.Imported imported) { // As OOO says: // The transforms for each target, each frame. Transform3D[][] transforms = imported.transforms; // So presumably this means that... // ...It's backwards! First dimension is the frame number, second dimension is the transform for each target. // big brain time // ... I swear they must've been waiting for someone like me to come along. Purposely being confusing. float fps = imported.rate; Animation animation = new Animation(name); int numIterations = transforms.Length; if (imported.skipLastFrame) { numIterations--; } SKAnimatorToolsProxy.IncrementEnd(numIterations); for (int frameIndex = 0; frameIndex < numIterations; frameIndex++) { Transform3D[] targetFrames = transforms[frameIndex]; Animation.Keyframe keyframe = new Animation.Keyframe(); for (int targetIndex = 0; targetIndex < imported.targets.Length; targetIndex++) { string target = imported.targets[targetIndex]; Transform3D transform = targetFrames[targetIndex]; // Catch case: Might be null. // Since I'm hellbent on doing things oddly for this part of the program, I'll manually interpolate lol if (transform == null) { Transform3D nextTransform = null; Transform3D previousTransform = null; int prevIndex = 0; int nextIndex = 0; // Start at 1 because if there's a single frame gap, then the result of ^ will be 2 // And then this will count as frame 1 instead of 0, causing 1/2 or 0.5. for (int aheadIndex = 0; aheadIndex < numIterations; aheadIndex++) { if (aheadIndex > frameIndex) { nextTransform = transforms[aheadIndex][targetIndex]; if (nextTransform != null) { nextIndex = aheadIndex; } } else { Transform3D prev = transforms[aheadIndex][targetIndex]; if (prev != null) { previousTransform = prev; prevIndex = aheadIndex; } } } int max = nextIndex - prevIndex; for (float progress = 0; progress < max; progress++) { int trsIndex = prevIndex + (int)progress + 1; transforms[trsIndex][targetIndex] = previousTransform.lerp(nextTransform, progress / max); } } keyframe.Keys.Add(new Animation.Key { Node = target, Transform = transform }); keyframe.Time = frameIndex / fps; } animation.Keyframes.Add(keyframe); SKAnimatorToolsProxy.IncrementProgress(); } foreach (Model3D model in attachToModels) { model.Animations.Add(animation); } } else if (animationImplementation is AnimationConfig.Sequential sequential) { //AnimationConfig.Implementation[] subs = new AnimationConfig.Implementation[sequential.animations.Length]; SKAnimatorToolsProxy.IncrementEnd(sequential.animations.Length); for (int index = 0; index < sequential.animations.Length; index++) { AnimationConfig.ComponentAnimation component = sequential.animations[index]; HandleAnimationImplementation(srcConfig, name, original, Dereference(component), attachToModels); SKAnimatorToolsProxy.IncrementProgress(); } /* * } else if (animationImplementation is AnimationConfig.Procedural proc) { * * if (srcFile.Name.StartsWith("rotate_")) { * // Rotation animation * // The first transform's first target is the node it attaches to. * * string axis = srcFile.Name.Substring(7, 1); * if (axis == "x") { * object speed = srcConfig.getArguments().get("Speed"); * string onNode = (string)srcConfig.getArguments().get("Node"); * // Big thing: Speed is probably a JAVA float, not a C# float * if (speed is java.lang.Float jfloat) speed = jfloat.floatValue(); * * Animation anim = HardcodedAnimations.CreateRotateX(onNode, (float)(speed ?? 1f)); * * IEnumerable<Model3D> targetModels = attachToModels.Where(model => model.RawName == onNode); * foreach (Model3D model in targetModels) { * model.Animations.Add(anim); * } * * } else if (axis == "y") { * object speed = srcConfig.getArguments().get("Speed"); * string onNode = (string)srcConfig.getArguments().get("Node"); * if (speed is java.lang.Float jfloat) speed = jfloat.floatValue(); * * Animation anim = HardcodedAnimations.CreateRotateY(onNode,(float)(speed ?? 1f)); * IEnumerable<Model3D> targetModels = attachToModels.Where(model => model.RawName == onNode); * foreach (Model3D model in targetModels) { * model.Animations.Add(anim); * } * * } else if (axis == "z") { * object speed = srcConfig.getArguments().get("Speed"); * string onNode = (string)srcConfig.getArguments().get("Node"); * if (speed is java.lang.Float jfloat) speed = jfloat.floatValue(); * * Animation anim = HardcodedAnimations.CreateRotateZ(onNode, (float)(speed ?? 1f)); * IEnumerable<Model3D> targetModels = attachToModels.Where(model => model.RawName == onNode); * foreach (Model3D model in targetModels) { * model.Animations.Add(anim); * } * * } else { * XanLogger.WriteLine(string.Format(ERR_PROC_NOT_YET_SUPPORTED, srcFile.Name), color: Color.DarkGoldenrod); * } * } else if (srcFile.Name == "gear_rotation.dat") { * // same as rotate y but it has an extra value * object speed = srcConfig.getArguments().get("Speed"); * object sizeRatio = srcConfig.getArguments().get("Size Ratio"); * string onNode = (string)srcConfig.getArguments().get("Node"); * if (speed is java.lang.Float jfloat) speed = jfloat.floatValue(); * if (sizeRatio is java.lang.Float jfloat2) sizeRatio = jfloat2.floatValue(); * * float overallSpeed = (float)(speed ?? 1f) * (float)(sizeRatio ?? 1f); * * Animation anim = HardcodedAnimations.CreateRotateY(onNode, overallSpeed); * IEnumerable<Model3D> targetModels = attachToModels.Where(model => model.RawName == onNode); * foreach (Model3D model in targetModels) { * model.Animations.Add(anim); * } * * } else { * XanLogger.WriteLine(string.Format(ERR_PROC_NOT_YET_SUPPORTED, srcFile.Name), color: Color.DarkGoldenrod); * } */ /* * // Something's wrong with directs that I gotta fix. * * } else if (animationImplementation is AnimationConfig.Procedural proc) { * AnimationConfig.TargetTransform[] targets = proc.transforms; * * * Animation animation = new Animation(name); * float timeIncrement = proc.duration / targets.Length; * int currentFrame = 0; * foreach (AnimationConfig.TargetTransform targetTrs in targets) { * Transform3DExpression expr = targetTrs.expression; * Transform3D transform = (Transform3D)expr.createEvaluator(null).evaluate(); * * Animation.Keyframe keyframe = new Animation.Keyframe(); * for (int targetIndex = 0; targetIndex < targetTrs.targets.Length; targetIndex++) { * * string target = targetTrs.targets[targetIndex]; * XanLogger.WriteLine(target, color: Color.Blue); * keyframe.Keys.Add(new Animation.Key { * Node = target, * Transform = transform * }); * keyframe.Time = currentFrame * timeIncrement; * } * animation.Keyframes.Add(keyframe); * currentFrame++; * } * * foreach (Model3D model in attachToModels) { * model.Animations.Add(animation); * } */ } else { XanLogger.WriteLine(string.Format(ERR_IMPL_NOT_SUPPORTED, animationImplementation.GetType().Name), color: Color.DarkGoldenrod); } SKAnimatorToolsProxy.IncrementProgress(); }