/// @func BBMOD_Model([_file[, _sha1]]) /// /// @extends BBMOD_Resource /// /// @implements {BBMOD_IRenderable} /// /// @desc A model. /// /// @param {String} [_file] The "*.bbmod" model file to load or `undefined`. /// Defaults to `undefined`. /// @param {String} [_sha1] Expected SHA1 of the file. If the actual /// one does not match with this, then the model will not be loaded. Use /// `undefined` if you do not want to check the SHA1 of the file. Defaults to /// `undefined`. /// /// @example /// ```gml /// try /// { /// modCharacter = new BBMOD_Model("Character.bbmod"); /// } /// catch (_error) /// { /// // The model failed to load! /// } /// ``` /// /// @throws {BBMOD_Exception} When the model fails to load. function BBMOD_Model(_file=undefined, _sha1=undefined) : BBMOD_Resource() constructor { BBMOD_CLASS_GENERATED_BODY; implement(BBMOD_IRenderable); static Resource_destroy = destroy; /// @var {Real} The major version of the model file. VersionMajor = BBMOD_VERSION_MAJOR; /// @var {Real} The minor version of the model file. VersionMinor = BBMOD_VERSION_MINOR; /// @var {Struct.BBMOD_VertexFormat} The vertex format of the model. /// @see BBMOD_VertexFormat /// @obsolete Since version 3.2 of the BBMOD file format each mesh has /// its own vertex format! /// @see BBMOD_Mesh.VertexFormat /// @readonly VertexFormat = undefined; /// @var {Array} Array of meshes. /// @readonly Meshes = []; /// @var {Real} Number of nodes. /// @readonly NodeCount = 0; /// @var {Struct.BBMOD_Node} The root node. /// @see BBMOD_Node /// @readonly RootNode = undefined; /// @var {Real} Number of bones. /// @readonly BoneCount = 0; /// @var {Array} An array of bone offset dual quaternions. /// @private __offsetArray = []; /// @var {Real} Number of materials that the model uses. /// @see BBMOD_BaseMaterial /// @readonly MaterialCount = 0; /// @var {Array} An array of material names. /// @see BBMOD_Model.Materials /// @see BBMOD_Model.get_material /// @see BBMOD_Model.set_material /// @readonly MaterialNames = []; /// @var {Array} An array of materials. Each entry /// defaults to {@link BBMOD_MATERIAL_DEFAULT}. /// @see BBMOD_Model.MaterialNames /// @see BBMOD_Model.get_material /// @see BBMOD_Model.set_material /// @see BBMOD_BaseMaterial Materials = []; /// @var {Bool} If `true` then the model is frozen. /// @readonly /// @see BBMOD_Model.freeze Frozen = false; /// @func copy(_dest) /// /// @desc Copies model data into another model. /// /// @param {Struct.BBMOD_Model} _dest The model to copy data to. /// /// @return {Struct.BBMOD_Model} Returns `self`. static copy = function (_dest) { _dest.IsLoaded = IsLoaded; _dest.Path = Path; _dest.VersionMajor = VersionMajor; _dest.VersionMinor = VersionMinor; _dest.VertexFormat = VertexFormat; for (var i = array_length(_dest.Meshes) - 1; i >= 0; --i) { _dest.Meshes[i].destroy(); } var _meshCount = array_length(Meshes); _dest.Meshes = array_create(_meshCount); for (var i = 0; i < _meshCount; ++i) { var _meshClone = Meshes[i].clone(); _meshClone.Model = _dest; _dest.Meshes[@ i] = _meshClone; } _dest.NodeCount = NodeCount; if (_dest.RootNode) { _dest.RootNode.destroy(); } if (RootNode) { _dest.RootNode = RootNode.clone(); _dest.__pass_self_to_nodes(); } else { _dest.RootNode = undefined; } _dest.BoneCount = BoneCount; _dest.__offsetArray = bbmod_array_clone(__offsetArray); _dest.MaterialCount = MaterialCount; _dest.MaterialNames = bbmod_array_clone(MaterialNames); _dest.Materials = bbmod_array_clone(Materials); _dest.Frozen = Frozen; return self; }; /// @func clone() /// /// @desc Creates a clone of the model. /// /// @return {Struct.BBMOD_Model} The created clone. static clone = function () { var _clone = new BBMOD_Model(); copy(_clone); return _clone; }; /// @func __pass_self_to_nodes([_node]) /// /// @desc /// /// @param {Struct.BBMOD_Node} [_node] /// /// @private static __pass_self_to_nodes = function (_node=undefined) { _node ??= RootNode; _node.Model = self; for (var i = array_length(_node.Children) - 1; i >= 0; --i) { __pass_self_to_nodes(_node.Children[i]); } return self; }; /// @func from_buffer(_buffer) /// /// @desc Loads model data from a buffer. /// /// @param {Id.Buffer} _buffer The buffer to load the data from. /// /// @return {Struct.BBMOD_Model} Returns `self`. /// /// @throws {BBMOD_Exception} If loading fails. static from_buffer = function (_buffer) { var _hasMinorVersion = false; var _type = buffer_read(_buffer, buffer_string); if (_type == "bbmod") { } else if (_type == "BBMOD") { _hasMinorVersion = true; } else { throw new BBMOD_Exception("Buffer does not contain a BBMOD!"); } VersionMajor = buffer_read(_buffer, buffer_u8); if (VersionMajor != BBMOD_VERSION_MAJOR) { throw new BBMOD_Exception( "Invalid BBMOD major version " + string(VersionMajor) + "!"); } if (_hasMinorVersion) { VersionMinor = buffer_read(_buffer, buffer_u8); if (VersionMinor > BBMOD_VERSION_MINOR) { throw new BBMOD_Exception( "Invalid BBMOD minor version " + string(VersionMinor) + "!"); } } else { VersionMinor = 0; } // Vertex format if (VersionMinor < 2) { VertexFormat = __bbmod_vertex_format_load(_buffer, VersionMinor); } // Meshes var _meshCount = buffer_read(_buffer, buffer_u32); Meshes = array_create(_meshCount, undefined); var i = 0; repeat (_meshCount) { Meshes[@ i++] = new BBMOD_Mesh(VertexFormat, self).from_buffer(_buffer); } // Node count and root node NodeCount = buffer_read(_buffer, buffer_u32); RootNode = new BBMOD_Node(self).from_buffer(_buffer); // Bone offsets BoneCount = buffer_read(_buffer, buffer_u32); if (BoneCount > 0) { __offsetArray = array_create(BoneCount * 8, 0); repeat (BoneCount) { var _index = buffer_read(_buffer, buffer_f32) * 8; // Bone index __offsetArray[@ _index + 0] = buffer_read(_buffer, buffer_f32); __offsetArray[@ _index + 1] = buffer_read(_buffer, buffer_f32); __offsetArray[@ _index + 2] = buffer_read(_buffer, buffer_f32); __offsetArray[@ _index + 3] = buffer_read(_buffer, buffer_f32); __offsetArray[@ _index + 4] = buffer_read(_buffer, buffer_f32); __offsetArray[@ _index + 5] = buffer_read(_buffer, buffer_f32); __offsetArray[@ _index + 6] = buffer_read(_buffer, buffer_f32); __offsetArray[@ _index + 7] = buffer_read(_buffer, buffer_f32); } } // Materials MaterialCount = buffer_read(_buffer, buffer_u32); if (MaterialCount > 0) { Materials = array_create(MaterialCount, BBMOD_MATERIAL_DEFAULT); var _materialNames = array_create(MaterialCount, undefined); i = 0; repeat (MaterialCount) { _materialNames[@ i++] = buffer_read(_buffer, buffer_string); } MaterialNames = _materialNames; } IsLoaded = true; return self; }; /// @func to_buffer(_buffer) /// /// @desc Writes model data to a buffer. /// /// @param {Id.Buffer} _buffer The buffer to write the data to. /// /// @return {Struct.BBMOD_Model} Returns `self`. static to_buffer = function (_buffer) { buffer_write(_buffer, buffer_string, "BBMOD"); buffer_write(_buffer, buffer_u8, VersionMajor); buffer_write(_buffer, buffer_u8, VersionMinor); // Vertex format if (VersionMinor < 2) { __bbmod_vertex_format_save(VertexFormat, _buffer, VersionMinor); } // Meshes var _meshCount = array_length(Meshes); buffer_write(_buffer, buffer_u32, _meshCount) var i = 0; repeat (_meshCount) { Meshes[i++].to_buffer(_buffer); } // Node count and root node buffer_write(_buffer, buffer_u32, NodeCount); RootNode.to_buffer(_buffer); // Bone offsets buffer_write(_buffer, buffer_u32, BoneCount); if (BoneCount > 0) { var _index = 0; repeat (BoneCount) { buffer_write(_buffer, buffer_f32, _index / 8); // Bone index buffer_write(_buffer, buffer_f32, __offsetArray[_index + 0]); buffer_write(_buffer, buffer_f32, __offsetArray[_index + 1]); buffer_write(_buffer, buffer_f32, __offsetArray[_index + 2]); buffer_write(_buffer, buffer_f32, __offsetArray[_index + 3]); buffer_write(_buffer, buffer_f32, __offsetArray[_index + 4]); buffer_write(_buffer, buffer_f32, __offsetArray[_index + 5]); buffer_write(_buffer, buffer_f32, __offsetArray[_index + 6]); buffer_write(_buffer, buffer_f32, __offsetArray[_index + 7]); _index += 8; } } // Materials buffer_write(_buffer, buffer_u32, MaterialCount); i = 0; repeat (MaterialCount) { buffer_write(_buffer, buffer_string, MaterialNames[i++]); } return self; }; /// @func freeze() /// /// @desc Freezes all vertex buffers used by the model. This should make its /// rendering faster, but it disables creating new batches of the model. /// /// @return {Struct.BBMOD_Model} Returns `self`. static freeze = function () { gml_pragma("forceinline"); if (!Frozen) { var i = 0; repeat (array_length(Meshes)) { Meshes[i++].freeze(); } Frozen = true; } return self; }; /// @func find_node(_idOrName[, _node]) /// /// @desc Finds a node by its name or id. /// /// @param {Real, String} _idOrName The id (real) or the name (string) of /// the node. /// @param {Struct.BBMOD_Node} [_node] The node to start searching from. /// Defaults to the root node. /// /// @return {Struct.BBMOD_Node} Returns the found node or `undefined`. static find_node = function (_idOrName, _node=RootNode) { var _isName = is_string(_idOrName); if (_isName && _node.Name == _idOrName) { return _node; } if (!_isName && _node.Index == _idOrName) { return _node; } var _children = _node.Children; var i = 0; repeat (array_length(_children)) { var _found = find_node(_idOrName, _children[i++]); if (_found != undefined) { return _found; } } return undefined; }; /// @func find_node_id(_nodeName) /// /// @desc Finds id of the model's node by its name. /// /// @param {String} _nodeName The name of the node. /// /// @return {Real} The id of the node or `undefined` when it is not found. /// /// @note It is not recommended to use this method in release builds, because /// having many of these lookups can slow down your game! You should instead /// use the ids available from the `_log.txt` files, which are created during /// model conversion. static find_node_id = function (_nodeName) { gml_pragma("forceinline"); var _node = find_node(_nodeName); if (_node != undefined) { return _node.Index; } return undefined; }; /// @func get_material(_name) /// /// @desc Retrieves a material by its name. /// /// @param {String} _name The name of the material. /// /// @return {Struct.BBMOD_BaseMaterial} The material. /// /// @throws {BBMOD_Exception} If the model does not have a material with /// given name. /// /// @see BBMOD_Model.Materials /// @see BBMOD_Model.MaterialNames /// @see BBMOD_Model.set_material /// @see BBMOD_BaseMaterial static get_material = function (_name) { var i = 0; repeat (MaterialCount) { if (MaterialNames[i] == _name) { return Materials[i]; } ++i; } throw new BBMOD_Exception("No such material found!"); }; /// @func set_material(_name, _material) /// /// @desc Sets a material. /// /// @param {String} _name The name of the material slot. /// @param {Struct.BBMOD_BaseMaterial} _material The material. /// /// @return {Struct.BBMOD_Model} Returns `self`. /// /// @throws {BBMOD_Exception} If the model does not have a material with /// given name. /// /// @see BBMOD_Model.Materials /// @see BBMOD_Model.MaterialNames /// @see BBMOD_Model.get_material /// @see BBMOD_BaseMaterial static set_material = function (_name, _material) { var i = 0; repeat (MaterialCount) { if (MaterialNames[i] == _name) { Materials[@ i] = _material; return self; } ++i; } throw new BBMOD_Exception("No such material found!"); }; /// @func get_vertex_format([_bones[, _ids]]) /// /// @desc Used to retrieve or create a vertex format compatible with the model. /// /// @param {Bool} [_bones] Use `true` to include bone data in the vertex /// format. Defaults to `true`. /// @param {Bool} [_ids] Use `true` to include model instance ids in the /// vertex format. Defaults to `false`. /// /// @deprecated Each {@link BBMOD_Mesh} now has its own vertex format! static get_vertex_format = function (_bones=true, _ids=false) { gml_pragma("forceinline"); var _vertexFormat = VertexFormat ? VertexFormat : Meshes[0].VertexFormat; return new BBMOD_VertexFormat( _vertexFormat.Vertices, _vertexFormat.Normals, _vertexFormat.TextureCoords, _vertexFormat.Colors, _vertexFormat.TangentW, _bones ? _vertexFormat.Bones : false, _ids); }; /// @func submit([_materials[, _transform[, _batchData]]]) /// /// @desc Immediately submits the model for rendering. /// /// @param {Array} [_materials] An array of /// materials, one for each material slot of the model. If not specified, /// then {@link BBMOD_Model.Materials} is used. Defaults to `undefined`. /// @param {Array} [_transform] An array of dual quaternions for /// transforming animated models or `undefined`. /// @param {Array, Array>} [_batchData] Data for dynamic /// batching or `undefined`. /// /// @return {Struct.BBMOD_Model} Returns `self`. /// /// @example /// ```gml /// bbmod_material_reset(); /// // Render a terrain model (does not have animation data) /// modTerrain.submit([mat_grass]); /// // Render a character model (animated by animationPlayer) /// modCharacter.submit([mat_head, mat_body], animationPlayer.get_transform()); /// bbmod_material_reset(); /// ``` /// /// @note Only parts of the model that use materials compatible with the /// current render pass are submitted! /// /// This method does not do anything if the model has not been loaded yet. /// /// @see BBMOD_Resource.IsLoaded /// @see BBMOD_BaseMaterial /// @see BBMOD_AnimationPlayer.get_transform /// @see bbmod_material_reset /// @see BBMOD_ERenderPass static submit = function (_materials=undefined, _transform=undefined, _batchData=undefined) { gml_pragma("forceinline"); if (RootNode != undefined) { _materials ??= Materials; RootNode.submit(_materials, _transform, _batchData); } return self; }; /// @func render([_materials[, _transform[, _batchData[, _matrix]]]]) /// /// @desc Enqueues the model for rendering. /// /// @param {Array} [_materials] An array of /// materials, one for each material slot of the model. If not specified, /// then {@link BBMOD_Model.Materials} is used. Defaults to `undefined`. /// @param {Array} [_transform] An array of dual quaternions for /// transforming animated models or `undefined`. /// @param {Array, Array>} [_batchData] Data for dynamic /// batching or `undefined`. /// @param {Array} [_matrix] The world matrix. Defaults to /// `matrix_get(matrix_world)`. /// /// @return {Struct.BBMOD_Model} Returns `self`. /// /// @note This method does not do anything if the model has not been loaded /// yet. /// /// @see BBMOD_Resource.IsLoaded /// @see BBMOD_BaseMaterial /// @see BBMOD_AnimationPlayer.get_transform /// @see bbmod_material_reset static render = function (_materials=undefined, _transform=undefined, _batchData=undefined, _matrix=undefined) { gml_pragma("forceinline"); if (RootNode != undefined) { _materials ??= Materials; if (_matrix == undefined) { _matrix = matrix_get(matrix_world); } RootNode.render(_materials, _transform, _batchData, _matrix); } return self; }; /// @func __to_dynamic_batch(_dynamicBatch) /// /// @param {Struct.BBMOD_DynamicBatch} _dynamicBatch /// /// @return {Struct.BBMOD_Model} Returns `self`. /// /// @private static __to_dynamic_batch = function (_dynamicBatch) { gml_pragma("forceinline"); var i = 0; repeat (array_length(Meshes)) { Meshes[i++].__to_dynamic_batch(_dynamicBatch); } return self; }; /// @func __to_static_batch(_staticBatch, _transform) /// /// @param {Struct.BBMOD_StaticBatch} _staticBatch /// @param {Array} _transform /// /// @return {Struct.BBMOD_Model} Returns `self`. /// /// @private static __to_static_batch = function (_staticBatch, _transform) { gml_pragma("forceinline"); var i = 0; repeat (array_length(Meshes)) { Meshes[i++].__to_static_batch(self, _staticBatch, _transform); } return self; }; static destroy = function () { Resource_destroy(); var i = 0; repeat (array_length(Meshes)) { Meshes[i++].destroy(); } Meshes = []; return undefined; }; if (_file != undefined) { from_file(_file, _sha1); } }