mirror of
https://github.com/godotengine/godot.git
synced 2024-11-10 14:12:51 +00:00
Merge pull request #61568 from lawnjelly/merge_node
[3.x] Add MergeGroup node to simplify merging Meshes at runtime
This commit is contained in:
commit
e96ebf9218
@ -17,7 +17,8 @@
|
||||
<members>
|
||||
<member name="allow_merging" type="bool" setter="set_allow_merging" getter="get_allow_merging" default="true">
|
||||
This allows fine control over the mesh merging feature in the [RoomManager].
|
||||
Setting this option to [code]false[/code] can be used to prevent an instance being merged.
|
||||
Setting this option to [code]false[/code] can be used to prevent an instance being merged. When set to [code]true[/code] (the default), merging will be determined by [member Spatial.merging_mode].
|
||||
[i]Deprecated.[/i] This property has been deprecated and is only included for backward compatibility. Please use [member Spatial.merging_mode] instead.
|
||||
</member>
|
||||
<member name="autoplace_priority" type="int" setter="set_portal_autoplace_priority" getter="get_portal_autoplace_priority" default="0">
|
||||
When set to [code]0[/code], [CullInstance]s will be autoplaced in the [Room] with the highest priority.
|
||||
|
114
doc/classes/MergeGroup.xml
Normal file
114
doc/classes/MergeGroup.xml
Normal file
@ -0,0 +1,114 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<class name="MergeGroup" inherits="Spatial" version="3.6" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
|
||||
<brief_description>
|
||||
MergeGroups allow merging of suitable meshes, which can enhance performance.
|
||||
</brief_description>
|
||||
<description>
|
||||
[MergeGroup] is a way of grouping nodes into logical blocks that contain meshes that are suitable for joining together, in order to increase rendering efficiency and reduce the number of nodes to simplify the scene.
|
||||
Only children and descendants will be considered for merging. [MergeGroup] has no effect on parents or siblings.
|
||||
Meshes must be static (non-moving) in relation to one another to be joined. For instance, a level background is often intended to be static. However, logical blocks that [b]move[/b] together, such as a ship, or car, are also good candidates for merging.
|
||||
Within these blocks you will often want to prevent certain nodes or branches from being merged, because they [b]are[/b] intended to move, or change visibility, in relation to the main block. An example might be a steering wheel on a ship. You can finely control this with [member Spatial.merging_mode]. Be aware that [member Spatial.merging_mode] will be inherited from parents and ancestors of the [MergeGroup].
|
||||
There are two ways of performing merging:
|
||||
- At runtime, using [method merge_meshes] or [member auto_merge].
|
||||
- Baking at design time to a separate scene, using the [code]bake[/code] button in the Editor inspector.
|
||||
Merging at runtime is usually best, because it is non-destructive, and will minimize the binary size of the [code]pck[/code] file. It can however take a small amount of time to merge the meshes (usually during level load), but this will usually be well under a second.
|
||||
Baking ahead of time allows fastest possible load times, but it is by nature a [i]destructive[/i] operation - you should keep a copy of the source scene for later editing, because you cannot reconstruct an unmerged scene from a baked scene. It can also bloat the size of the [code]pck[/code] file considerably, as for example storing 10 merged trees will have 10x the geometry of the scene before merging.
|
||||
On the other hand, baking ahead of time is very useful for previewing what will happen after merging, and diagnosing problems. It is also convenient for some workflows such as constructing a scene out of merged modular units.
|
||||
</description>
|
||||
<tutorials>
|
||||
</tutorials>
|
||||
<methods>
|
||||
<method name="get_param">
|
||||
<return type="int" />
|
||||
<argument index="0" name="param" type="int" enum="MergeGroup.Param" />
|
||||
<description>
|
||||
Returns the value of the specified [enum MergeGroup.Param] parameter.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_param_enabled">
|
||||
<return type="bool" />
|
||||
<argument index="0" name="param" type="int" enum="MergeGroup.ParamEnabled" />
|
||||
<description>
|
||||
Gets the value of the specified [enum MergeGroup.ParamEnabled] parameter.
|
||||
</description>
|
||||
</method>
|
||||
<method name="merge_meshes">
|
||||
<return type="void" />
|
||||
<description>
|
||||
You can choose to either automatically merge when the [MergeGroup] enters the scene (usually during loading) using [member auto_merge], or you can manually trigger merging by calling this function.
|
||||
Manually activating merging is especially useful when you are [i]procedurally generating[/i] your level, and when you want to set advanced parameters prior to merging at runtime.
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_param">
|
||||
<return type="void" />
|
||||
<argument index="0" name="param" type="int" enum="MergeGroup.Param" />
|
||||
<argument index="1" name="value" type="int" />
|
||||
<description>
|
||||
Sets the value of the specified [enum MergeGroup.Param] parameter.
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_param_enabled">
|
||||
<return type="void" />
|
||||
<argument index="0" name="param" type="int" enum="MergeGroup.ParamEnabled" />
|
||||
<argument index="1" name="value" type="bool" />
|
||||
<description>
|
||||
Sets the value of the specified [enum MergeGroup.ParamEnabled] parameter.
|
||||
</description>
|
||||
</method>
|
||||
</methods>
|
||||
<members>
|
||||
<member name="auto_merge" type="bool" setter="set_param_enabled" getter="get_param_enabled" default="true">
|
||||
Activates merging automatically when the [MergeGroup] enters the scene (usually during loading).
|
||||
Alternatively you can switch this off and use [method merge_meshes] to manually activate merging.
|
||||
</member>
|
||||
<member name="shadow_proxy" type="bool" setter="set_param_enabled" getter="get_param_enabled" default="true">
|
||||
If [code]true[/code], a [b]shadow proxy[/b] will be generated. This is a merged mesh that is a duplicate of the existing opaque geometry, set to cast shadows only. The source meshes will have shadow casting switched off.
|
||||
This can be more efficient for rendering shadows, because the requirements for merging a [b]shadow mesh[/b] are far lower than for regular merging. Providing materials are opaque, meshes with different materials can often be merged together for the purposes of shadow casting. This can reduce drawcalls.
|
||||
[b]Tip:[/b] Try running with and without a [b]shadow proxy[/b] and measure performance, sometimes it will be faster, sometimes not.
|
||||
</member>
|
||||
</members>
|
||||
<constants>
|
||||
<constant name="PARAM_ENABLED_AUTO_MERGE" value="0" enum="ParamEnabled">
|
||||
Activates merging automatically when the [MergeGroup] enters the scene (usually during loading).
|
||||
Alternatively you can switch this off and use [method merge_meshes] to manually activate merging.
|
||||
</constant>
|
||||
<constant name="PARAM_ENABLED_SHADOW_PROXY" value="1" enum="ParamEnabled">
|
||||
If [code]true[/code], a [b]shadow proxy[/b] will be generated. This is a merged mesh that is a duplicate of the existing opaque geometry, set to cast shadows only. The source meshes will have shadow casting switched off.
|
||||
This can be more efficient for rendering shadows, because the requirements for merging a [b]shadow mesh[/b] are far lower than for regular merging. Providing materials are opaque, meshes with different materials can often be merged together for the purposes of shadow casting. This can reduce drawcalls.
|
||||
[b]Tip:[/b] Try running with and without a [b]shadow proxy[/b] and measure performance, sometimes it will be faster, sometimes not.
|
||||
</constant>
|
||||
<constant name="PARAM_ENABLED_CONVERT_CSGS" value="2" enum="ParamEnabled">
|
||||
If [code]true[/code], [code]CSG[/code] nodes will be converted to [MeshInstance]s. These [MeshInstance]s can then be merged if suitable matches are found.
|
||||
</constant>
|
||||
<constant name="PARAM_ENABLED_CONVERT_GRIDMAPS" value="3" enum="ParamEnabled">
|
||||
If [code]true[/code], [GridMap]s will be converted to [MeshInstance]s. These [MeshInstance]s can then be merged if suitable matches are found.
|
||||
[b]Note:[/b] [GridMap]s are usually rendered as [MultiMesh]es very efficiently, so converting these will often be counterproductive. Exceptions include when using the [code]GLES2[/code] backend, which can be inefficient at rendering [MultiMesh].
|
||||
</constant>
|
||||
<constant name="PARAM_ENABLED_COMBINE_SURFACES" value="4" enum="ParamEnabled">
|
||||
If [code]true[/code], as a final step, matching [MeshInstance]s can be joined by combining their surfaces to form an [i]"uber mesh instance"[/i].
|
||||
While this is convenient, it does have the downside that all the constituent meshes will be culled as one unit, which can make culling less efficient in some situations.
|
||||
</constant>
|
||||
<constant name="PARAM_ENABLED_CLEAN_MESHES" value="5" enum="ParamEnabled">
|
||||
Cleans and removes degenerate triangles from meshes, which can make them more suitable for later processing, such as generating secondary UVs for lightmapping.
|
||||
[b]Note:[/b] This step can be slow and should typically only be used when [i]baking[/i] the [MergeGroup].
|
||||
</constant>
|
||||
<constant name="PARAM_GROUP_SIZE" value="0" enum="Param">
|
||||
When set to [code]0[/code], all matching meshes will be merged within the [MergeGroup].
|
||||
If set to [code]1[/code] or above, only groups of a maximum of [code]group_size[/code] meshes will be merged together. These groups will be chosen by locality. This enables getting some of the benefits of merging, while still allowing some culling to take place.
|
||||
[b]Tip:[/b] Use [i]baking[/i] to preview what the scene will look like after merging.
|
||||
</constant>
|
||||
<constant name="PARAM_SPLITS_HORIZONTAL" value="1" enum="Param">
|
||||
When set to a value above [code]1[/code], mesh geometry will be [i]split by locality[/i] into a grid of [MeshInstance]s.
|
||||
For instance a value of [code]2[/code] will split meshes into a grid of 2x2 (on the [code]x[/code] and [code]z[/code] axes), for greater culling efficiency.
|
||||
[b]Note:[/b] Greater culling efficiency must be balanced against a greater number of drawcalls.
|
||||
</constant>
|
||||
<constant name="PARAM_SPLITS_VERTICAL" value="2" enum="Param">
|
||||
This setting acts exactly as [constant PARAM_SPLITS_HORIZONTAL], except it determines the grid split on the vertical axis.
|
||||
A grid with [constant PARAM_SPLITS_HORIZONTAL] [code]3[/code], and [constant PARAM_SPLITS_VERTICAL] [code]2[/code] will produce a grid of 3x2x3 (on the [code]x[/code] and [code]y[/code] and [code]z[/code] axes respectively).
|
||||
</constant>
|
||||
<constant name="PARAM_MIN_SPLIT_POLY_COUNT" value="3" enum="Param">
|
||||
When using [i]split by locality[/i] using [constant PARAM_SPLITS_HORIZONTAL] and / or [constant PARAM_SPLITS_VERTICAL], you can specify that the split will only occur for meshes above this specified poly count.
|
||||
There is often little to gain by splitting meshes with low poly count.
|
||||
</constant>
|
||||
</constants>
|
||||
</class>
|
@ -65,6 +65,7 @@
|
||||
<method name="is_mergeable_with" qualifiers="const">
|
||||
<return type="bool" />
|
||||
<argument index="0" name="other_mesh_instance" type="Node" />
|
||||
<argument index="1" name="shadows_only" type="bool" default="false" />
|
||||
<description>
|
||||
Returns [code]true[/code] if this [MeshInstance] can be merged with the specified [code]other_mesh_instance[/code], using the [method MeshInstance.merge_meshes] function.
|
||||
In order to be mergeable, properties of the [MeshInstance] must match, and each surface must match, in terms of material, attributes and vertex format.
|
||||
@ -72,9 +73,10 @@
|
||||
</method>
|
||||
<method name="merge_meshes">
|
||||
<return type="bool" />
|
||||
<argument index="0" name="mesh_instances" type="Array" default="[ ]" />
|
||||
<argument index="0" name="mesh_instances" type="Array" />
|
||||
<argument index="1" name="use_global_space" type="bool" default="false" />
|
||||
<argument index="2" name="check_compatibility" type="bool" default="true" />
|
||||
<argument index="3" name="shadows_only" type="bool" default="false" />
|
||||
<description>
|
||||
This function can merge together the data from several source [MeshInstance]s into a single destination [MeshInstance] (the MeshInstance the function is called from). This is primarily useful for improving performance by reducing the number of drawcalls and [Node]s.
|
||||
Merging should only be attempted for simple meshes that do not contain animation.
|
||||
|
@ -266,6 +266,11 @@
|
||||
<member name="global_translation" type="Vector3" setter="set_global_translation" getter="get_global_translation">
|
||||
Global position of this node. This is equivalent to [code]global_transform.origin[/code].
|
||||
</member>
|
||||
<member name="merging_mode" type="int" setter="set_merging_mode" getter="get_merging_mode" enum="Spatial.MergingMode" default="0">
|
||||
The merging mode determines whether merging features of the engine ([MergeGroup] and [RoomManager]) will attempt to operate on branches of the scene tree.
|
||||
The default mode inherited from the scene tree root is [constant MERGING_MODE_ON].
|
||||
[b]Note:[/b] Merging mode determines whether the merging is [b]allowed[/b] to be performed. It does not guarantee that merging will occur, which depends on whether there are suitable matching objects.
|
||||
</member>
|
||||
<member name="position" type="Vector3" setter="set_translation" getter="get_translation">
|
||||
Local position of this node. This is a forward-compatible alias for [member translation].
|
||||
</member>
|
||||
@ -328,5 +333,14 @@
|
||||
<constant name="NOTIFICATION_EXIT_GAMEPLAY" value="46">
|
||||
Spatial nodes receives this notification if the portal system gameplay monitor detects they have exited the gameplay area.
|
||||
</constant>
|
||||
<constant name="MERGING_MODE_INHERIT" value="0" enum="MergingMode">
|
||||
Inherits merging mode from the node's parent. For the root node, it is equivalent to [constant MERGING_MODE_ON]. Default.
|
||||
</constant>
|
||||
<constant name="MERGING_MODE_OFF" value="1" enum="MergingMode">
|
||||
Turn off merging in this node and children set to [constant MERGING_MODE_INHERIT].
|
||||
</constant>
|
||||
<constant name="MERGING_MODE_ON" value="2" enum="MergingMode">
|
||||
Turn on merging in this node and children set to [constant MERGING_MODE_INHERIT].
|
||||
</constant>
|
||||
</constants>
|
||||
</class>
|
||||
|
@ -135,6 +135,7 @@
|
||||
#include "editor/plugins/light_occluder_2d_editor_plugin.h"
|
||||
#include "editor/plugins/line_2d_editor_plugin.h"
|
||||
#include "editor/plugins/material_editor_plugin.h"
|
||||
#include "editor/plugins/merge_group_editor_plugin.h"
|
||||
#include "editor/plugins/mesh_editor_plugin.h"
|
||||
#include "editor/plugins/mesh_instance_editor_plugin.h"
|
||||
#include "editor/plugins/mesh_library_editor_plugin.h"
|
||||
@ -7063,6 +7064,7 @@ EditorNode::EditorNode() {
|
||||
add_editor_plugin(memnew(OccluderEditorPlugin(this)));
|
||||
add_editor_plugin(memnew(PortalEditorPlugin(this)));
|
||||
add_editor_plugin(memnew(PackedSceneEditorPlugin(this)));
|
||||
add_editor_plugin(memnew(MergeGroupEditorPlugin(this)));
|
||||
add_editor_plugin(memnew(Path2DEditorPlugin(this)));
|
||||
add_editor_plugin(memnew(PathEditorPlugin(this)));
|
||||
add_editor_plugin(memnew(Line2DEditorPlugin(this)));
|
||||
|
@ -126,6 +126,7 @@ EditorPropertyNameProcessor::EditorPropertyNameProcessor() {
|
||||
capitalize_string_remaps["commentfocus"] = "Comment Focus";
|
||||
capitalize_string_remaps["cpu"] = "CPU";
|
||||
capitalize_string_remaps["csg"] = "CSG";
|
||||
capitalize_string_remaps["csgs"] = "CSGs";
|
||||
capitalize_string_remaps["db"] = "dB";
|
||||
capitalize_string_remaps["defaultfocus"] = "Default Focus";
|
||||
capitalize_string_remaps["defaultframe"] = "Default Frame";
|
||||
|
1
editor/icons/icon_merge_group.svg
Normal file
1
editor/icons/icon_merge_group.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><g fill="none" stroke="#fc9c9c" stroke-opacity=".996078" stroke-width="2"><path d="m5 9.0002631c0 1.0093139.0423651 4.9997369 3 4.9997369 2.983771 0 2.999965-3.993807 3-4.9997369" stroke-linejoin="round"/><path d="m1.980385 1.992121h4.03923v4.015757h-4.03923z"/><path d="m9.997205 1.976528h4.03923v4.015757h-4.03923z"/></g></svg>
|
After Width: | Height: | Size: 413 B |
586
editor/plugins/merge_group_editor_plugin.cpp
Normal file
586
editor/plugins/merge_group_editor_plugin.cpp
Normal file
@ -0,0 +1,586 @@
|
||||
/**************************************************************************/
|
||||
/* merge_group_editor_plugin.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#include "merge_group_editor_plugin.h"
|
||||
|
||||
#include "core/io/resource_saver.h"
|
||||
#include "editor/spatial_editor_gizmos.h"
|
||||
#include "scene/3d/mesh_instance.h"
|
||||
#include "scene/resources/merging_tool.h"
|
||||
#include "scene/resources/packed_scene.h"
|
||||
|
||||
EditorProgress *MergeGroupEditorPlugin::tmp_progress = nullptr;
|
||||
EditorProgress *MergeGroupEditorPlugin::tmp_subprogress = nullptr;
|
||||
|
||||
void MergeGroupEditorBakeDialog::_bake_confirm() {
|
||||
_owner_plugin->dialog_pressed_bake(_single_scene->is_pressed(), (int)_subscene_polycount_threshold->get_value());
|
||||
}
|
||||
|
||||
void MergeGroupEditorBakeDialog::_add_bake_checkbox(Node *p_parent, CheckBox **pp_checkbox, const String &p_text, const String &p_tooltip, bool p_default) {
|
||||
*pp_checkbox = memnew(CheckBox);
|
||||
(*pp_checkbox)->set_text(TTR(p_text));
|
||||
(*pp_checkbox)->set_tooltip(TTR(p_tooltip));
|
||||
(*pp_checkbox)->set_pressed(p_default);
|
||||
p_parent->add_child(*pp_checkbox);
|
||||
}
|
||||
|
||||
void MergeGroupEditorBakeDialog::_add_bake_spinbox(VBoxContainer *p_parent, SpinBox **pp_spinbox, const String &p_text, const String &p_tooltip, int32_t p_min, int32_t p_max, int32_t p_step, int32_t p_default) {
|
||||
*pp_spinbox = memnew(SpinBox);
|
||||
(*pp_spinbox)->set_min(p_min);
|
||||
(*pp_spinbox)->set_max(p_max);
|
||||
(*pp_spinbox)->set_step(p_step);
|
||||
(*pp_spinbox)->set_value(p_default);
|
||||
(*pp_spinbox)->set_tooltip(p_tooltip);
|
||||
p_parent->add_margin_child(TTR(p_text), *pp_spinbox);
|
||||
}
|
||||
|
||||
void MergeGroupEditorBakeDialog::fill_merge_group_params(MergeGroup &r_merge_group) {
|
||||
r_merge_group.set_param_enabled(MergeGroup::PARAM_ENABLED_SHADOW_PROXY, _shadow_proxy->is_pressed());
|
||||
r_merge_group.set_param_enabled(MergeGroup::PARAM_ENABLED_CONVERT_CSGS, _convert_csgs->is_pressed());
|
||||
r_merge_group.set_param_enabled(MergeGroup::PARAM_ENABLED_CONVERT_GRIDMAPS, _convert_gridmaps->is_pressed());
|
||||
r_merge_group.set_param_enabled(MergeGroup::PARAM_ENABLED_COMBINE_SURFACES, _combine_surfaces->is_pressed());
|
||||
r_merge_group.set_param_enabled(MergeGroup::PARAM_ENABLED_CLEAN_MESHES, _clean_meshes->is_pressed());
|
||||
|
||||
r_merge_group.set_param(MergeGroup::PARAM_GROUP_SIZE, _group_size->get_value());
|
||||
r_merge_group.set_param(MergeGroup::PARAM_SPLITS_HORIZONTAL, _splits_horz->get_value());
|
||||
r_merge_group.set_param(MergeGroup::PARAM_SPLITS_VERTICAL, _splits_vert->get_value());
|
||||
r_merge_group.set_param(MergeGroup::PARAM_MIN_SPLIT_POLY_COUNT, _min_split_poly_count->get_value());
|
||||
}
|
||||
|
||||
MergeGroupEditorBakeDialog::MergeGroupEditorBakeDialog(MergeGroupEditorPlugin *p_owner) {
|
||||
_owner_plugin = p_owner;
|
||||
|
||||
set_title("Bake MergeGroup");
|
||||
|
||||
get_ok()->connect("pressed", this, "_bake_confirm");
|
||||
|
||||
VBoxContainer *vbc = memnew(VBoxContainer);
|
||||
add_child(vbc);
|
||||
|
||||
_add_bake_checkbox(vbc, &_single_scene, "Single Scene", "Save meshes as a single scene or separate scenes.", true);
|
||||
|
||||
_add_bake_spinbox(vbc, &_subscene_polycount_threshold, "Subscene Polycount Threshold:", "Threshold polycount to split scenes into separate subscenes.", 0, 1024 * 128, 64, 1024);
|
||||
|
||||
_add_bake_checkbox(vbc, &_shadow_proxy, "Shadow Proxy", "Separate meshes for shadow rendering.", true);
|
||||
_add_bake_checkbox(vbc, &_convert_csgs, "Convert CSGs", "Convert CSGs to meshes so they can be merged.", true);
|
||||
_add_bake_checkbox(vbc, &_convert_gridmaps, "Convert GridMaps", "Convert GridMaps to meshes so they can be merged.");
|
||||
_add_bake_checkbox(vbc, &_combine_surfaces, "Combine Surfaces", "Combine merged surfaces to form an \"ubermesh\".", true);
|
||||
_add_bake_checkbox(vbc, &_clean_meshes, "Clean Meshes", "Clean geometry data, remove degenerate triangles.");
|
||||
|
||||
_add_bake_spinbox(vbc, &_group_size, "Group Size", "When non-zero, only local groups of the corresponding number of meshes will be merged.", 0, 128, 1, 0);
|
||||
_add_bake_spinbox(vbc, &_splits_horz, "Splits Horizontal", "When above 1, the final meshes will be split into a grid on the horizontal axis.", 0, 16, 1, 1);
|
||||
_add_bake_spinbox(vbc, &_splits_vert, "Splits Vertical", "When above 1, the final meshes will be split into a grid on the vertical axis.", 0, 16, 1, 1);
|
||||
_add_bake_spinbox(vbc, &_min_split_poly_count, "Min Split Polycount", "When splitting by grid, only meshes above this minimum polycount will be split.", 0, 1024 * 128, 256, 1024);
|
||||
}
|
||||
|
||||
void MergeGroupEditorBakeDialog::_bind_methods() {
|
||||
ClassDB::bind_method("_bake_confirm", &MergeGroupEditorBakeDialog::_bake_confirm);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
|
||||
bool MergeGroupEditorPlugin::bake_func_step(float p_progress, const String &p_description, void *, bool p_force_refresh) {
|
||||
if (!tmp_progress) {
|
||||
tmp_progress = memnew(EditorProgress("bake_merge_group", TTR("Bake MergeGroup"), 1000, true));
|
||||
ERR_FAIL_NULL_V(tmp_progress, false);
|
||||
}
|
||||
return tmp_progress->step(p_description, p_progress * 1000, p_force_refresh);
|
||||
}
|
||||
|
||||
bool MergeGroupEditorPlugin::bake_func_substep(float p_progress, const String &p_description, void *, bool p_force_refresh) {
|
||||
if (!tmp_subprogress) {
|
||||
tmp_subprogress = memnew(EditorProgress("bake_merge_group_substep", "", 1000, true));
|
||||
ERR_FAIL_NULL_V(tmp_subprogress, false);
|
||||
}
|
||||
return tmp_subprogress->step(p_description, p_progress * 1000, p_force_refresh);
|
||||
}
|
||||
|
||||
void MergeGroupEditorPlugin::bake_func_end(uint32_t p_time_started) {
|
||||
if (tmp_progress != nullptr) {
|
||||
memdelete(tmp_progress);
|
||||
tmp_progress = nullptr;
|
||||
}
|
||||
|
||||
if (tmp_subprogress != nullptr) {
|
||||
memdelete(tmp_subprogress);
|
||||
tmp_subprogress = nullptr;
|
||||
}
|
||||
|
||||
const int time_taken = (OS::get_singleton()->get_ticks_msec() - p_time_started) * 0.001;
|
||||
if (time_taken >= 1) {
|
||||
// Only print a message and request attention if baking took at least 1 second.
|
||||
print_line(vformat("Done baking MergeGroup in %02d:%02d:%02d.", time_taken / 3600, (time_taken % 3600) / 60, time_taken % 60));
|
||||
|
||||
// Request attention in case the user was doing something else.
|
||||
OS::get_singleton()->request_attention();
|
||||
}
|
||||
}
|
||||
|
||||
void MergeGroupEditorPlugin::dialog_pressed_bake(bool p_single_scene, int p_subscene_polycount_threshold) {
|
||||
if (!_merge_group) {
|
||||
return;
|
||||
}
|
||||
|
||||
_params.single_scene = p_single_scene;
|
||||
_params.subscene_polycount_threshold = p_subscene_polycount_threshold;
|
||||
|
||||
Node *root = _merge_group->get_tree()->get_edited_scene_root();
|
||||
|
||||
if (root == _merge_group) {
|
||||
EditorNode::get_singleton()->show_warning(TTR("Cannot bake MergeGroup when it is scene root."));
|
||||
return;
|
||||
}
|
||||
|
||||
String scene_path = _merge_group->get_filename();
|
||||
if (scene_path == String()) {
|
||||
scene_path = _merge_group->get_owner()->get_filename();
|
||||
}
|
||||
if (scene_path == String()) {
|
||||
EditorNode::get_singleton()->show_warning(TTR("Can't determine a save path for merge group.\nSave your scene and try again."));
|
||||
return;
|
||||
}
|
||||
scene_path = scene_path.get_basename() + ".tscn";
|
||||
|
||||
file_dialog->set_current_path(scene_path);
|
||||
file_dialog->popup_centered_ratio();
|
||||
}
|
||||
|
||||
bool MergeGroupEditorPlugin::_find_visual_instances_recursive(Node *p_node) {
|
||||
if (Object::cast_to<VisualInstance>(p_node)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (int n = 0; n < p_node->get_child_count(); n++) {
|
||||
if (_find_visual_instances_recursive(p_node->get_child(n))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void MergeGroupEditorPlugin::_bake() {
|
||||
ERR_FAIL_NULL(_merge_group);
|
||||
|
||||
// If the merge group does not contain any VisualInstance children, flag an error.
|
||||
if (!_find_visual_instances_recursive(_merge_group)) {
|
||||
EditorNode::get_singleton()->show_warning(TTR("MergeGroup does not contain any VisualInstances.\nCannot Bake."));
|
||||
return;
|
||||
}
|
||||
|
||||
bake_dialog->show();
|
||||
}
|
||||
|
||||
Spatial *MergeGroupEditorPlugin::_convert_merge_group_to_spatial(MergeGroup *p_merge_group) {
|
||||
ERR_FAIL_NULL_V(p_merge_group, nullptr);
|
||||
Node *parent = p_merge_group->get_parent();
|
||||
ERR_FAIL_NULL_V(parent, nullptr);
|
||||
|
||||
Spatial *spatial = memnew(Spatial);
|
||||
parent->add_child(spatial);
|
||||
|
||||
// They can't have the same name at the same time.
|
||||
String name = p_merge_group->get_name();
|
||||
p_merge_group->set_name(name + "_temp");
|
||||
spatial->set_name(name);
|
||||
|
||||
// Identical transforms.
|
||||
spatial->set_transform(p_merge_group->get_transform());
|
||||
|
||||
// Move the children.
|
||||
// GODOT is abysmally bad at moving children in order unfortunately.
|
||||
// So reverse order for now.
|
||||
for (int n = p_merge_group->get_child_count() - 1; n >= 0; n--) {
|
||||
Node *child = p_merge_group->get_child(n);
|
||||
p_merge_group->remove_child(child);
|
||||
spatial->add_child(child);
|
||||
}
|
||||
|
||||
// Change owners.
|
||||
MergingTool::_invalidate_owner_recursive(spatial, nullptr, p_merge_group->get_owner());
|
||||
|
||||
// Delete AND detach the merge group from the tree.
|
||||
p_merge_group->_delete_node(p_merge_group);
|
||||
|
||||
return spatial;
|
||||
}
|
||||
|
||||
void MergeGroupEditorPlugin::_bake_select_file(const String &p_file) {
|
||||
if (!_merge_group) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Special treatment for scene root.
|
||||
Node *root = _merge_group->get_tree()->get_edited_scene_root();
|
||||
|
||||
// Cannot bake scene root.
|
||||
if (root == _merge_group) {
|
||||
EditorNode::get_singleton()->show_warning(TTR("Cannot bake scene root.\nPlease move to a branch before baking."));
|
||||
ERR_FAIL_COND(root == _merge_group);
|
||||
}
|
||||
|
||||
Node *parent = _merge_group->get_parent();
|
||||
ERR_FAIL_NULL(parent);
|
||||
|
||||
// Disallow saving to the same scene as the root scene
|
||||
// (this is usually user error), and prevents losing work.
|
||||
if (root->get_filename() == p_file) {
|
||||
EditorNode::get_singleton()->show_warning(TTR("Cannot save to the currently edited scene.\nPlease save to a different scene."));
|
||||
ERR_FAIL_COND(root->get_filename() == p_file);
|
||||
}
|
||||
|
||||
// Ensure to reset this when exiting this routine!
|
||||
// Spatial gizmos, especially for meshes are very expensive
|
||||
// in terms of RAM and performance, and are totally
|
||||
// unnecessary for temporary objects
|
||||
SpatialEditor::_prevent_gizmo_generation = true;
|
||||
|
||||
#ifdef GODOT_MERGING_VERBOSE
|
||||
MergingTool::debug_branch(_merge_group, "START_SCENE");
|
||||
#endif
|
||||
|
||||
Spatial *hanger = memnew(Spatial);
|
||||
hanger->set_name("hanger");
|
||||
parent->add_child(hanger);
|
||||
hanger->set_owner(_merge_group->get_owner());
|
||||
|
||||
uint32_t time_start = OS::get_singleton()->get_ticks_msec();
|
||||
bake_func_step(0.0, "Duplicating Branch", nullptr, true);
|
||||
|
||||
_duplicate_branch(_merge_group, hanger);
|
||||
|
||||
// Temporarily hide source branch, to speed things up in the editor.
|
||||
bool was_visible = _merge_group->is_visible_in_tree();
|
||||
_merge_group->hide();
|
||||
|
||||
MergeGroup *merge_group_copy = Object::cast_to<MergeGroup>(hanger->get_child(0));
|
||||
|
||||
// Set the parameters in the copy mergegroup to those set up in the bake dialog.
|
||||
bake_dialog->fill_merge_group_params(*merge_group_copy);
|
||||
|
||||
if (merge_group_copy->merge_meshes_in_editor()) {
|
||||
if (!bake_func_step(1.0, "Saving Scene", nullptr, true)) {
|
||||
// Convert the merge node to a spatial..
|
||||
// Once baked we don't want baked scenes to be merged AGAIN
|
||||
// when incorporated into scenes.
|
||||
Spatial *final_branch = _convert_merge_group_to_spatial(merge_group_copy);
|
||||
|
||||
// Only save if not cancelled by user.
|
||||
_save_scene(final_branch, p_file);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef GODOT_MERGING_VERBOSE
|
||||
MergingTool::debug_branch(hanger, "END_SCENE");
|
||||
#endif
|
||||
|
||||
// Finished.
|
||||
hanger->queue_delete();
|
||||
_merge_group->set_visible(was_visible);
|
||||
|
||||
SpatialEditor::_prevent_gizmo_generation = false;
|
||||
|
||||
bake_func_end(time_start);
|
||||
}
|
||||
|
||||
void MergeGroupEditorPlugin::_remove_queue_deleted_nodes_recursive(Node *p_node) {
|
||||
if (p_node->is_queued_for_deletion()) {
|
||||
p_node->get_parent()->remove_child(p_node);
|
||||
return;
|
||||
}
|
||||
|
||||
for (int n = p_node->get_child_count() - 1; n >= 0; n--) {
|
||||
_remove_queue_deleted_nodes_recursive(p_node->get_child(n));
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t MergeGroupEditorPlugin::_get_mesh_poly_count(const MeshInstance &p_mi) const {
|
||||
Ref<Mesh> rmesh = p_mi.get_mesh();
|
||||
if (rmesh.is_valid()) {
|
||||
return rmesh->get_face_count();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool MergeGroupEditorPlugin::_replace_with_branch_scene(const String &p_file, Node *p_base) {
|
||||
Node *old_owner = p_base->get_owner();
|
||||
|
||||
Ref<PackedScene> sdata = ResourceLoader::load(p_file);
|
||||
if (!sdata.is_valid()) {
|
||||
ERR_PRINT("Error loading scene from \"" + p_file + "\".");
|
||||
return false;
|
||||
}
|
||||
|
||||
Node *instanced_scene = sdata->instance(PackedScene::GEN_EDIT_STATE_INSTANCE);
|
||||
if (!instanced_scene) {
|
||||
ERR_PRINT("Error instancing scene from \"" + p_file + "\".");
|
||||
return false;
|
||||
}
|
||||
|
||||
Node *parent = p_base->get_parent();
|
||||
int pos = p_base->get_index();
|
||||
|
||||
parent->remove_child(p_base);
|
||||
parent->add_child(instanced_scene);
|
||||
parent->move_child(instanced_scene, pos);
|
||||
|
||||
List<Node *> owned;
|
||||
p_base->get_owned_by(p_base->get_owner(), &owned);
|
||||
Array owners;
|
||||
for (List<Node *>::Element *F = owned.front(); F; F = F->next()) {
|
||||
owners.push_back(F->get());
|
||||
}
|
||||
|
||||
instanced_scene->set_owner(old_owner);
|
||||
|
||||
p_base->queue_delete();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MergeGroupEditorPlugin::_save_subscene(Node *p_root, Node *p_branch, String p_base_filename, int &r_subscene_count) {
|
||||
bake_func_substep(0.0, p_branch->get_name(), nullptr, false);
|
||||
|
||||
Node *scene_root = p_root;
|
||||
|
||||
Map<Node *, Node *> reown;
|
||||
reown[scene_root] = p_branch;
|
||||
|
||||
Node *copy = p_branch->duplicate_and_reown(reown);
|
||||
|
||||
bake_func_substep(0.2, p_branch->get_name(), nullptr, false);
|
||||
|
||||
if (copy) {
|
||||
Ref<PackedScene> sdata = memnew(PackedScene);
|
||||
Error err = sdata->pack(copy);
|
||||
memdelete(copy);
|
||||
|
||||
bake_func_substep(0.4, p_branch->get_name(), nullptr, false);
|
||||
|
||||
if (err != OK) {
|
||||
WARN_PRINT("Couldn't save subscene \"" + p_branch->get_name() + "\" . Likely dependencies (instances) couldn't be satisfied. Saving as part of main scene instead.");
|
||||
return false;
|
||||
}
|
||||
|
||||
String filename = p_base_filename + "_" + itos(r_subscene_count++) + ".scn";
|
||||
|
||||
#ifdef DEV_ENABLED
|
||||
print_verbose("Save subscene: " + filename);
|
||||
#endif
|
||||
|
||||
err = ResourceSaver::save(filename, sdata, ResourceSaver::FLAG_COMPRESS);
|
||||
|
||||
bake_func_substep(0.6, p_branch->get_name(), nullptr, false);
|
||||
|
||||
if (err != OK) {
|
||||
WARN_PRINT("Error saving subscene \"" + p_branch->get_name() + "\" , saving as part of main scene instead.");
|
||||
return false;
|
||||
}
|
||||
_replace_with_branch_scene(filename, p_branch);
|
||||
} else {
|
||||
WARN_PRINT("Error duplicating subscene \"" + p_branch->get_name() + "\" , saving as part of main scene instead.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MergeGroupEditorPlugin::_save_mesh_subscenes_recursive(Node *p_root, Node *p_node, String p_base_filename, int &r_subscene_count) {
|
||||
if (p_node->is_queued_for_deletion()) {
|
||||
return;
|
||||
}
|
||||
// Is a subscene already?
|
||||
if (p_node->get_filename().length() && (p_node != p_root)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Is it a mesh instance?
|
||||
MeshInstance *mi = Object::cast_to<MeshInstance>(p_node);
|
||||
|
||||
// Don't save subscenes for trivially sized meshes.
|
||||
if (mi && (!_params.subscene_polycount_threshold || ((int)_get_mesh_poly_count(*mi) > _params.subscene_polycount_threshold))) {
|
||||
// Save as subscene.
|
||||
if (_save_subscene(p_root, p_node, p_base_filename, r_subscene_count)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Replaced subscenes will be added to the last child, so going in reverse order is necessary.
|
||||
for (int n = p_node->get_child_count() - 1; n >= 0; n--) {
|
||||
_save_mesh_subscenes_recursive(p_root, p_node->get_child(n), p_base_filename, r_subscene_count);
|
||||
}
|
||||
}
|
||||
|
||||
void MergeGroupEditorPlugin::_push_mesh_data_to_gpu_recursive(Node *p_node) {
|
||||
// Is it a mesh instance?
|
||||
MeshInstance *mi = Object::cast_to<MeshInstance>(p_node);
|
||||
|
||||
if (mi) {
|
||||
Ref<Mesh> rmesh = mi->get_mesh();
|
||||
if (rmesh.is_valid()) {
|
||||
rmesh->set_storage_mode(Mesh::STORAGE_MODE_GPU);
|
||||
}
|
||||
}
|
||||
|
||||
for (int n = 0; n < p_node->get_child_count(); n++) {
|
||||
_push_mesh_data_to_gpu_recursive(p_node->get_child(n));
|
||||
}
|
||||
}
|
||||
|
||||
bool MergeGroupEditorPlugin::_save_scene(Node *p_branch, String p_filename) {
|
||||
// For some reason the saving machinery doesn't deal well with nodes queued for deletion,
|
||||
// so we will remove them from the scene tree (as risk of leaks, but the queue delete machinery
|
||||
// should still work when detached).
|
||||
_remove_queue_deleted_nodes_recursive(p_branch);
|
||||
|
||||
// All mesh data must be on the GPU for the Mesh saving routines to work.
|
||||
_push_mesh_data_to_gpu_recursive(p_branch);
|
||||
|
||||
Node *scene_root = p_branch->get_tree()->get_edited_scene_root();
|
||||
|
||||
Map<Node *, Node *> reown;
|
||||
reown[scene_root] = p_branch;
|
||||
|
||||
Node *copy = p_branch->duplicate_and_reown(reown);
|
||||
|
||||
#ifdef GODOT_MERGING_VERBOSE
|
||||
MergingTool::debug_branch(copy, "SAVE SCENE:");
|
||||
#endif
|
||||
|
||||
bake_func_substep(0.0, p_filename, nullptr, false);
|
||||
|
||||
if (copy) {
|
||||
// Save any large meshes as compressed resources.
|
||||
if (!_params.single_scene) {
|
||||
int subscene_count = 0;
|
||||
_save_mesh_subscenes_recursive(copy, copy, p_filename.get_basename(), subscene_count);
|
||||
}
|
||||
|
||||
bake_func_substep(0.4, p_filename, nullptr, false);
|
||||
|
||||
Ref<PackedScene> sdata = memnew(PackedScene);
|
||||
Error err = sdata->pack(copy);
|
||||
memdelete(copy);
|
||||
|
||||
bake_func_substep(0.8, p_filename, nullptr, false);
|
||||
|
||||
if (err != OK) {
|
||||
EditorNode::get_singleton()->show_warning(TTR("Couldn't save merged branch.\nLikely dependencies (instances) couldn't be satisfied."));
|
||||
return false;
|
||||
}
|
||||
|
||||
err = ResourceSaver::save(p_filename, sdata, ResourceSaver::FLAG_COMPRESS);
|
||||
if (err != OK) {
|
||||
EditorNode::get_singleton()->show_warning(TTR("Error saving scene."));
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
EditorNode::get_singleton()->show_warning(TTR("Error duplicating scene to save it."));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MergeGroupEditorPlugin::_duplicate_branch(Node *p_branch, Node *p_new_parent) {
|
||||
Node *dup = p_branch->duplicate();
|
||||
|
||||
ERR_FAIL_NULL(dup);
|
||||
|
||||
p_new_parent->add_child(dup);
|
||||
|
||||
Node *new_owner = p_new_parent->get_owner();
|
||||
dup->set_owner(new_owner);
|
||||
|
||||
MergingTool::_invalidate_owner_recursive(dup, nullptr, new_owner);
|
||||
}
|
||||
|
||||
void MergeGroupEditorPlugin::edit(Object *p_object) {
|
||||
MergeGroup *mg = Object::cast_to<MergeGroup>(p_object);
|
||||
if (!mg) {
|
||||
return;
|
||||
}
|
||||
|
||||
_merge_group = mg;
|
||||
}
|
||||
|
||||
bool MergeGroupEditorPlugin::handles(Object *p_object) const {
|
||||
return p_object->is_class("MergeGroup");
|
||||
}
|
||||
|
||||
void MergeGroupEditorPlugin::make_visible(bool p_visible) {
|
||||
if (p_visible) {
|
||||
button_bake->show();
|
||||
} else {
|
||||
button_bake->hide();
|
||||
bake_dialog->hide();
|
||||
}
|
||||
}
|
||||
|
||||
void MergeGroupEditorPlugin::_bind_methods() {
|
||||
ClassDB::bind_method("_bake", &MergeGroupEditorPlugin::_bake);
|
||||
ClassDB::bind_method("_bake_select_file", &MergeGroupEditorPlugin::_bake_select_file);
|
||||
}
|
||||
|
||||
MergeGroupEditorPlugin::MergeGroupEditorPlugin(EditorNode *p_node) {
|
||||
editor = p_node;
|
||||
|
||||
button_bake = memnew(ToolButton);
|
||||
button_bake->set_icon(editor->get_gui_base()->get_icon("Bake", "EditorIcons"));
|
||||
button_bake->set_text(TTR("Bake"));
|
||||
button_bake->set_tooltip(TTR("Bake MergeGroup to Scene."));
|
||||
button_bake->hide();
|
||||
button_bake->connect("pressed", this, "_bake");
|
||||
|
||||
file_dialog = memnew(EditorFileDialog);
|
||||
file_dialog->set_mode(EditorFileDialog::MODE_SAVE_FILE);
|
||||
file_dialog->add_filter("*.tscn ; " + TTR("Scene"));
|
||||
file_dialog->add_filter("*.scn ; " + TTR("Binary Scene"));
|
||||
file_dialog->set_title(TTR("Save Merged Scene As..."));
|
||||
file_dialog->connect("file_selected", this, "_bake_select_file");
|
||||
button_bake->add_child(file_dialog);
|
||||
|
||||
bake_dialog = memnew(MergeGroupEditorBakeDialog(this));
|
||||
bake_dialog->set_anchors_and_margins_preset(Control::PRESET_CENTER);
|
||||
bake_dialog->hide();
|
||||
button_bake->add_child(bake_dialog);
|
||||
|
||||
add_control_to_container(CONTAINER_SPATIAL_EDITOR_MENU, button_bake);
|
||||
|
||||
_merge_group = nullptr;
|
||||
|
||||
MergeGroup::bake_step_function = bake_func_step;
|
||||
MergeGroup::bake_substep_function = bake_func_substep;
|
||||
MergeGroup::bake_end_function = bake_func_end;
|
||||
}
|
||||
|
||||
MergeGroupEditorPlugin::~MergeGroupEditorPlugin() {
|
||||
}
|
121
editor/plugins/merge_group_editor_plugin.h
Normal file
121
editor/plugins/merge_group_editor_plugin.h
Normal file
@ -0,0 +1,121 @@
|
||||
/**************************************************************************/
|
||||
/* merge_group_editor_plugin.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef MERGE_GROUP_EDITOR_PLUGIN_H
|
||||
#define MERGE_GROUP_EDITOR_PLUGIN_H
|
||||
|
||||
#include "editor/editor_node.h"
|
||||
#include "editor/editor_plugin.h"
|
||||
#include "scene/3d/merge_group.h"
|
||||
#include "scene/resources/material.h"
|
||||
|
||||
class MergeGroupEditorPlugin;
|
||||
|
||||
class MergeGroupEditorBakeDialog : public ConfirmationDialog {
|
||||
GDCLASS(MergeGroupEditorBakeDialog, ConfirmationDialog);
|
||||
|
||||
MergeGroupEditorPlugin *_owner_plugin = nullptr;
|
||||
CheckBox *_single_scene = nullptr;
|
||||
SpinBox *_subscene_polycount_threshold = nullptr;
|
||||
|
||||
CheckBox *_shadow_proxy = nullptr;
|
||||
CheckBox *_convert_csgs = nullptr;
|
||||
CheckBox *_convert_gridmaps = nullptr;
|
||||
CheckBox *_combine_surfaces = nullptr;
|
||||
CheckBox *_clean_meshes = nullptr;
|
||||
SpinBox *_group_size = nullptr;
|
||||
SpinBox *_splits_horz = nullptr;
|
||||
SpinBox *_splits_vert = nullptr;
|
||||
SpinBox *_min_split_poly_count = nullptr;
|
||||
|
||||
void _bake_confirm();
|
||||
void _add_bake_checkbox(Node *p_parent, CheckBox **pp_checkbox, const String &p_text, const String &p_tooltip, bool p_default = false);
|
||||
void _add_bake_spinbox(VBoxContainer *p_parent, SpinBox **pp_spinbox, const String &p_text, const String &p_tooltip, int32_t p_min, int32_t p_max, int32_t p_step, int32_t p_default);
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void fill_merge_group_params(MergeGroup &r_merge_group);
|
||||
MergeGroupEditorBakeDialog(MergeGroupEditorPlugin *p_owner);
|
||||
};
|
||||
|
||||
class MergeGroupEditorPlugin : public EditorPlugin {
|
||||
GDCLASS(MergeGroupEditorPlugin, EditorPlugin);
|
||||
|
||||
MergeGroup *_merge_group;
|
||||
ToolButton *button_bake;
|
||||
EditorFileDialog *file_dialog;
|
||||
EditorNode *editor;
|
||||
MergeGroupEditorBakeDialog *bake_dialog;
|
||||
|
||||
struct Params {
|
||||
bool single_scene = false;
|
||||
int subscene_polycount_threshold = 1024;
|
||||
} _params;
|
||||
|
||||
static EditorProgress *tmp_progress;
|
||||
static EditorProgress *tmp_subprogress;
|
||||
|
||||
static bool bake_func_step(float p_progress, const String &p_description, void *, bool p_force_refresh);
|
||||
static bool bake_func_substep(float p_progress, const String &p_description, void *, bool p_force_refresh);
|
||||
static void bake_func_end(uint32_t p_time_started);
|
||||
|
||||
void _bake();
|
||||
void _bake_select_file(const String &p_file);
|
||||
void _duplicate_branch(Node *p_branch, Node *p_new_parent);
|
||||
bool _save_scene(Node *p_branch, String p_filename);
|
||||
void _remove_queue_deleted_nodes_recursive(Node *p_node);
|
||||
void _push_mesh_data_to_gpu_recursive(Node *p_node);
|
||||
Spatial *_convert_merge_group_to_spatial(MergeGroup *p_merge_group);
|
||||
bool _find_visual_instances_recursive(Node *p_node);
|
||||
|
||||
void _save_mesh_subscenes_recursive(Node *p_root, Node *p_node, String p_base_filename, int &r_subscene_count);
|
||||
bool _save_subscene(Node *p_root, Node *p_branch, String p_base_filename, int &r_subscene_count);
|
||||
bool _replace_with_branch_scene(const String &p_file, Node *p_base);
|
||||
uint32_t _get_mesh_poly_count(const MeshInstance &p_mi) const;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
virtual String get_name() const { return "MergeGroup"; }
|
||||
bool has_main_screen() const { return false; }
|
||||
virtual void edit(Object *p_object);
|
||||
virtual bool handles(Object *p_object) const;
|
||||
virtual void make_visible(bool p_visible);
|
||||
|
||||
void dialog_pressed_bake(bool p_single_scene, int p_subscene_polycount_threshold);
|
||||
|
||||
MergeGroupEditorPlugin(EditorNode *p_node);
|
||||
~MergeGroupEditorPlugin();
|
||||
};
|
||||
|
||||
#endif // MERGE_GROUP_EDITOR_PLUGIN_H
|
@ -77,6 +77,8 @@
|
||||
#define MIN_FOV 0.01
|
||||
#define MAX_FOV 179
|
||||
|
||||
bool SpatialEditor::_prevent_gizmo_generation = false;
|
||||
|
||||
void ViewportNavigationControl::_notification(int p_what) {
|
||||
if (p_what == NOTIFICATION_ENTER_TREE) {
|
||||
if (!is_connected("mouse_exited", this, "_on_mouse_exited")) {
|
||||
@ -6685,6 +6687,10 @@ void SpatialEditor::move_control_to_right_panel(Control *p_control) {
|
||||
}
|
||||
|
||||
void SpatialEditor::_request_gizmo(Object *p_obj) {
|
||||
if (_prevent_gizmo_generation) {
|
||||
return;
|
||||
}
|
||||
|
||||
Spatial *sp = Object::cast_to<Spatial>(p_obj);
|
||||
if (!sp) {
|
||||
return;
|
||||
|
@ -834,6 +834,10 @@ public:
|
||||
int get_over_gizmo_handle() const { return over_gizmo_handle; }
|
||||
void set_over_gizmo_handle(int idx) { over_gizmo_handle = idx; }
|
||||
|
||||
// Simple way to turn off (expensive) gizmo generation
|
||||
// especially for temporary objects in the editor.
|
||||
static bool _prevent_gizmo_generation;
|
||||
|
||||
void set_can_preview(Camera *p_preview);
|
||||
void set_message(String p_message, float p_time = 5);
|
||||
|
||||
|
@ -29,6 +29,8 @@
|
||||
/**************************************************************************/
|
||||
|
||||
#include "csg_shape.h"
|
||||
#include "scene/3d/mesh_instance.h"
|
||||
#include "scene/resources/merging_tool.h"
|
||||
|
||||
void CSGShape::set_use_collision(bool p_enable) {
|
||||
if (use_collision == p_enable) {
|
||||
@ -492,6 +494,50 @@ PoolVector<Vector3> CSGShape::get_brush_faces() {
|
||||
return faces;
|
||||
}
|
||||
|
||||
bool CSGShape::split_by_surface(Vector<Variant> p_destination_mesh_instances) {
|
||||
ERR_FAIL_COND_V_MSG(!is_inside_tree(), false, "Source CSGShape must be inside the SceneTree.");
|
||||
|
||||
// For simplicity we are requiring that the destination MeshInstances have the same parent
|
||||
// as the source. This means we can use identical transforms.
|
||||
Node *parent = get_parent();
|
||||
ERR_FAIL_NULL_V_MSG(parent, false, "Source CSGShape must have a parent node.");
|
||||
|
||||
// Bound function only support variants, so we need to convert to a list of MeshInstances.
|
||||
Vector<MeshInstance *> mis;
|
||||
|
||||
for (int n = 0; n < p_destination_mesh_instances.size(); n++) {
|
||||
MeshInstance *mi = Object::cast_to<MeshInstance>(p_destination_mesh_instances[n]);
|
||||
if (mi) {
|
||||
ERR_FAIL_COND_V_MSG(mi->get_parent() != parent, false, "Destination MeshInstances must be siblings of the source CSGShape.");
|
||||
mis.push_back(mi);
|
||||
} else {
|
||||
ERR_FAIL_V_MSG(false, "Only MeshInstances can be split.");
|
||||
}
|
||||
}
|
||||
|
||||
force_update_shape();
|
||||
|
||||
Array arr = get_meshes();
|
||||
ERR_FAIL_COND_V(arr.empty(), false);
|
||||
|
||||
Ref<ArrayMesh> arr_mesh = arr[1];
|
||||
ERR_FAIL_COND_V(!arr_mesh.is_valid(), false);
|
||||
|
||||
int num_surfaces = arr_mesh->get_surface_count();
|
||||
ERR_FAIL_COND_V(num_surfaces == 0, false);
|
||||
|
||||
ERR_FAIL_COND_V_MSG(mis.size() != num_surfaces, false, "Number of surfaces and number of destination MeshInstances must match.");
|
||||
|
||||
CSGBrush *brush = _get_brush();
|
||||
ERR_FAIL_NULL_V_MSG(brush, false, "Cannot get CSGBrush.");
|
||||
|
||||
for (int s = 0; s < num_surfaces; s++) {
|
||||
MergingTool::split_csg_surface_to_mesh_instance(*this, *mis[s], arr_mesh, brush, s);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
PoolVector<Face3> CSGShape::get_faces(uint32_t p_usage_flags) const {
|
||||
return PoolVector<Face3>();
|
||||
}
|
||||
|
@ -39,6 +39,8 @@
|
||||
#include "scene/resources/concave_polygon_shape.h"
|
||||
#include "thirdparty/misc/mikktspace.h"
|
||||
|
||||
class MeshInstance;
|
||||
|
||||
class CSGShape : public GeometryInstance {
|
||||
GDCLASS(CSGShape, GeometryInstance);
|
||||
|
||||
@ -132,6 +134,8 @@ public:
|
||||
virtual AABB get_aabb() const;
|
||||
virtual PoolVector<Face3> get_faces(uint32_t p_usage_flags) const;
|
||||
|
||||
bool split_by_surface(Vector<Variant> p_destination_mesh_instances);
|
||||
|
||||
void set_use_collision(bool p_enable);
|
||||
bool is_using_collision() const;
|
||||
|
||||
|
@ -33,8 +33,10 @@
|
||||
VARIANT_ENUM_CAST(CullInstance::PortalMode);
|
||||
|
||||
void CullInstance::set_portal_mode(CullInstance::PortalMode p_mode) {
|
||||
_portal_mode = p_mode;
|
||||
_refresh_portal_mode();
|
||||
if (p_mode != _portal_mode) {
|
||||
_portal_mode = p_mode;
|
||||
_refresh_portal_mode();
|
||||
}
|
||||
}
|
||||
|
||||
CullInstance::PortalMode CullInstance::get_portal_mode() const {
|
||||
|
@ -37,7 +37,7 @@ class CullInstance : public Spatial {
|
||||
GDCLASS(CullInstance, Spatial);
|
||||
|
||||
public:
|
||||
enum PortalMode {
|
||||
enum PortalMode : unsigned int {
|
||||
PORTAL_MODE_STATIC, // not moving within a room
|
||||
PORTAL_MODE_DYNAMIC, // moving within room
|
||||
PORTAL_MODE_ROAMING, // moving between rooms
|
||||
|
1193
scene/3d/merge_group.cpp
Normal file
1193
scene/3d/merge_group.cpp
Normal file
File diff suppressed because it is too large
Load Diff
154
scene/3d/merge_group.h
Normal file
154
scene/3d/merge_group.h
Normal file
@ -0,0 +1,154 @@
|
||||
/**************************************************************************/
|
||||
/* merge_group.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef MERGE_GROUP_H
|
||||
#define MERGE_GROUP_H
|
||||
|
||||
#include "spatial.h"
|
||||
|
||||
class MeshInstance;
|
||||
class CSGShape;
|
||||
class GridMap;
|
||||
|
||||
class MergeGroup : public Spatial {
|
||||
GDCLASS(MergeGroup, Spatial);
|
||||
friend class MergeGroupEditorPlugin;
|
||||
|
||||
public:
|
||||
enum ParamEnabled {
|
||||
PARAM_ENABLED_AUTO_MERGE,
|
||||
PARAM_ENABLED_SHADOW_PROXY,
|
||||
PARAM_ENABLED_CONVERT_CSGS,
|
||||
PARAM_ENABLED_CONVERT_GRIDMAPS,
|
||||
PARAM_ENABLED_COMBINE_SURFACES,
|
||||
PARAM_ENABLED_CLEAN_MESHES,
|
||||
PARAM_ENABLED_MAX,
|
||||
};
|
||||
|
||||
enum Param {
|
||||
PARAM_GROUP_SIZE,
|
||||
PARAM_SPLITS_HORIZONTAL,
|
||||
PARAM_SPLITS_VERTICAL,
|
||||
PARAM_MIN_SPLIT_POLY_COUNT,
|
||||
PARAM_MAX,
|
||||
};
|
||||
|
||||
void merge_meshes();
|
||||
bool merge_meshes_in_editor();
|
||||
|
||||
void set_param_enabled(ParamEnabled p_param, bool p_enabled);
|
||||
bool get_param_enabled(ParamEnabled p_param);
|
||||
|
||||
void set_param(Param p_param, int p_value);
|
||||
int get_param(Param p_param);
|
||||
|
||||
// These enable feedback in the Godot UI as we bake.
|
||||
typedef bool (*BakeStepFunc)(float, const String &, void *, bool); // Progress, step description, userdata, force refresh.
|
||||
typedef void (*BakeEndFunc)(uint32_t); // time_started
|
||||
|
||||
static BakeStepFunc bake_step_function;
|
||||
static BakeStepFunc bake_substep_function;
|
||||
static BakeEndFunc bake_end_function;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
void _notification(int p_what);
|
||||
|
||||
private:
|
||||
struct MeshAABB {
|
||||
MeshInstance *mi = nullptr;
|
||||
AABB aabb;
|
||||
static int _sort_axis;
|
||||
bool operator<(const MeshAABB &p_b) const {
|
||||
real_t a_min = aabb.position.coord[_sort_axis];
|
||||
real_t b_min = p_b.aabb.position.coord[_sort_axis];
|
||||
return a_min < b_min;
|
||||
}
|
||||
};
|
||||
|
||||
// Main function.
|
||||
bool _merge_meshes();
|
||||
|
||||
// Merging.
|
||||
void _find_mesh_instances_recursive(int p_depth, Node *p_node, LocalVector<MeshInstance *> &r_mis, bool p_shadows, bool p_flag_invalid_meshes = false);
|
||||
bool _merge_similar(LocalVector<MeshInstance *> &r_mis, bool p_shadows);
|
||||
void _merge_list(const LocalVector<MeshInstance *> &p_mis, bool p_shadows, int p_whittle_group = -1);
|
||||
void _merge_list_ex(const LocalVector<MeshAABB> &p_mesh_aabbs, bool p_shadows, int p_whittle_group = -1);
|
||||
bool _join_similar(LocalVector<MeshInstance *> &r_mis);
|
||||
void _split_mesh_by_surface(MeshInstance *p_mi, int p_num_surfaces);
|
||||
bool _split_by_locality();
|
||||
|
||||
// Helper funcs.
|
||||
void _convert_source_to_spatial(Spatial *p_node);
|
||||
void _reset_mesh_instance(MeshInstance *p_mi);
|
||||
void _move_children(Node *p_from, Node *p_to, bool p_recalculate_transforms = false);
|
||||
void _recursive_tree_merge(int &r_whittle_group, LocalVector<MeshAABB> p_list);
|
||||
void _delete_node(Node *p_node);
|
||||
bool _node_ok_to_delete(Node *p_node);
|
||||
void _cleanup_source_meshes(LocalVector<MeshInstance *> &r_cleanup_list);
|
||||
void _delete_dangling_spatials(Node *p_node);
|
||||
|
||||
void _node_changed(Node *p_node);
|
||||
void _node_changed_internal(Node *p_node);
|
||||
|
||||
// CSG
|
||||
void _find_csg_recursive(int p_depth, Node *p_node, LocalVector<CSGShape *> &r_csgs);
|
||||
void _split_csg_by_surface(CSGShape *p_shape);
|
||||
|
||||
bool _terminate_search(Node *p_node);
|
||||
|
||||
// Gridmap
|
||||
void _find_gridmap_recursive(int p_depth, Node *p_node, LocalVector<GridMap *> &r_gridmaps);
|
||||
void _bake_gridmap(GridMap *p_gridmap);
|
||||
|
||||
void _log(String p_string);
|
||||
void _logt(int p_tabs, String p_string);
|
||||
|
||||
struct Data {
|
||||
bool params_enabled[PARAM_ENABLED_MAX];
|
||||
uint32_t params[PARAM_MAX];
|
||||
|
||||
// Hidden Params.
|
||||
bool delete_sources = false;
|
||||
bool convert_sources = false;
|
||||
bool split_by_surface = false;
|
||||
|
||||
// Each merge is an iteration.
|
||||
uint32_t iteration = 0;
|
||||
Node *scene_root = nullptr;
|
||||
|
||||
Data();
|
||||
} data;
|
||||
};
|
||||
|
||||
VARIANT_ENUM_CAST(MergeGroup::Param);
|
||||
VARIANT_ENUM_CAST(MergeGroup::ParamEnabled);
|
||||
|
||||
#endif // MERGE_GROUP_H
|
@ -35,6 +35,7 @@
|
||||
#include "core/project_settings.h"
|
||||
#include "physics_body.h"
|
||||
#include "scene/resources/material.h"
|
||||
#include "scene/resources/merging_tool.h"
|
||||
#include "scene/scene_string_names.h"
|
||||
#include "servers/visual/visual_server_globals.h"
|
||||
#include "skeleton.h"
|
||||
@ -853,8 +854,50 @@ void MeshInstance::create_debug_tangents() {
|
||||
}
|
||||
}
|
||||
|
||||
bool MeshInstance::merge_meshes(Vector<Variant> p_list, bool p_use_global_space, bool p_check_compatibility) {
|
||||
// bound function only support variants, so we need to convert to a list of MeshInstances
|
||||
bool MeshInstance::split_by_surface(Vector<Variant> p_destination_mesh_instances) {
|
||||
ERR_FAIL_COND_V_MSG(!is_inside_tree(), false, "Source MeshInstance must be inside the SceneTree.");
|
||||
|
||||
// For simplicity we are requiring that the destination MeshInstances have the same parent
|
||||
// as the source. This means we can use identical transforms.
|
||||
Node *parent = get_parent();
|
||||
ERR_FAIL_NULL_V_MSG(parent, false, "Source MeshInstance must have a parent node.");
|
||||
|
||||
// Bound function only support variants, so we need to convert to a list of MeshInstances.
|
||||
Vector<MeshInstance *> mis;
|
||||
|
||||
for (int n = 0; n < p_destination_mesh_instances.size(); n++) {
|
||||
MeshInstance *mi = Object::cast_to<MeshInstance>(p_destination_mesh_instances[n]);
|
||||
if (mi) {
|
||||
if (mi != this) {
|
||||
ERR_FAIL_COND_V_MSG(mi->get_parent() != parent, false, "Destination MeshInstances must be siblings of the source MeshInstance.");
|
||||
|
||||
mis.push_back(mi);
|
||||
} else {
|
||||
ERR_FAIL_V_MSG(false, "Source MeshInstance cannot be a destination.");
|
||||
}
|
||||
} else {
|
||||
ERR_FAIL_V_MSG(false, "Only MeshInstances can be split.");
|
||||
}
|
||||
}
|
||||
|
||||
ERR_FAIL_COND_V_MSG(!get_mesh().is_valid(), false, "Mesh is invalid.");
|
||||
ERR_FAIL_COND_V_MSG(mis.size() != get_mesh()->get_surface_count(), false, "Number of surfaces and number of destination MeshInstances must match.");
|
||||
|
||||
// Go through each surface, and fill the relevant mesh instance.
|
||||
const Mesh *source_mesh = get_mesh().ptr();
|
||||
DEV_ASSERT(source_mesh);
|
||||
|
||||
ERR_FAIL_COND_V_MSG(source_mesh->get_surface_count() <= 1, false, "Source MeshInstance must contain multiple surfaces.");
|
||||
|
||||
for (int s = 0; s < source_mesh->get_surface_count(); s++) {
|
||||
MergingTool::split_surface_to_mesh_instance(*this, s, *mis[s]);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MeshInstance::merge_meshes(Vector<Variant> p_list, bool p_use_global_space, bool p_check_compatibility, bool p_shadows_only) {
|
||||
// Bound function only support variants, so we need to convert to a list of MeshInstances.
|
||||
Vector<MeshInstance *> mis;
|
||||
|
||||
for (int n = 0; n < p_list.size(); n++) {
|
||||
@ -871,480 +914,27 @@ bool MeshInstance::merge_meshes(Vector<Variant> p_list, bool p_use_global_space,
|
||||
}
|
||||
|
||||
ERR_FAIL_COND_V(!mis.size(), "Array contains no MeshInstances");
|
||||
return _merge_meshes(mis, p_use_global_space, p_check_compatibility);
|
||||
|
||||
if (p_shadows_only) {
|
||||
return MergingTool::merge_shadow_meshes(*this, mis, p_use_global_space, p_check_compatibility);
|
||||
}
|
||||
return MergingTool::merge_meshes(*this, mis, p_use_global_space, p_check_compatibility);
|
||||
}
|
||||
|
||||
bool MeshInstance::is_mergeable_with(Node *p_other) const {
|
||||
bool MeshInstance::is_mergeable_with(Node *p_other, bool p_shadows_only) const {
|
||||
const MeshInstance *mi = Object::cast_to<MeshInstance>(p_other);
|
||||
|
||||
if (mi) {
|
||||
return _is_mergeable_with(*mi);
|
||||
if (p_shadows_only) {
|
||||
return MergingTool::is_shadow_mergeable_with(*this, *mi);
|
||||
} else {
|
||||
return MergingTool::is_mergeable_with(*this, *mi, true);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MeshInstance::_is_mergeable_with(const MeshInstance &p_other) const {
|
||||
if (!get_mesh().is_valid() || !p_other.get_mesh().is_valid()) {
|
||||
return false;
|
||||
}
|
||||
if (!get_allow_merging() || !p_other.get_allow_merging()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// various settings that must match
|
||||
if (get_material_overlay() != p_other.get_material_overlay()) {
|
||||
return false;
|
||||
}
|
||||
if (get_material_override() != p_other.get_material_override()) {
|
||||
return false;
|
||||
}
|
||||
if (get_cast_shadows_setting() != p_other.get_cast_shadows_setting()) {
|
||||
return false;
|
||||
}
|
||||
if (get_flag(FLAG_USE_BAKED_LIGHT) != p_other.get_flag(FLAG_USE_BAKED_LIGHT)) {
|
||||
return false;
|
||||
}
|
||||
if (is_visible() != p_other.is_visible()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Ref<Mesh> rmesh_a = get_mesh();
|
||||
Ref<Mesh> rmesh_b = p_other.get_mesh();
|
||||
|
||||
int num_surfaces = rmesh_a->get_surface_count();
|
||||
if (num_surfaces != rmesh_b->get_surface_count()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int n = 0; n < num_surfaces; n++) {
|
||||
// materials must match
|
||||
if (get_active_material(n) != p_other.get_active_material(n)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// formats must match
|
||||
uint32_t format_a = rmesh_a->surface_get_format(n);
|
||||
uint32_t format_b = rmesh_b->surface_get_format(n);
|
||||
|
||||
if (format_a != format_b) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE : These three commented out sections below are more conservative
|
||||
// checks for whether to allow mesh merging. I am not absolutely sure a priori
|
||||
// how conservative we need to be, so we can further enable this if testing
|
||||
// shows they are required.
|
||||
|
||||
// if (get_surface_material_count() != p_other.get_surface_material_count()) {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// for (int n = 0; n < get_surface_material_count(); n++) {
|
||||
// if (get_surface_material(n) != p_other.get_surface_material(n)) {
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
|
||||
// test only allow identical meshes
|
||||
// if (get_mesh() != p_other.get_mesh()) {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MeshInstance::_merge_into_mesh_data(const MeshInstance &p_mi, const Transform &p_dest_tr_inv, int p_surface_id, LocalVector<Vector3> &r_verts, LocalVector<Vector3> &r_norms, LocalVector<real_t> &r_tangents, LocalVector<Color> &r_colors, LocalVector<Vector2> &r_uvs, LocalVector<Vector2> &r_uv2s, LocalVector<int> &r_inds) {
|
||||
_merge_log("\t\t\tmesh data from " + p_mi.get_name());
|
||||
|
||||
// get the mesh verts in local space
|
||||
Ref<Mesh> rmesh = p_mi.get_mesh();
|
||||
|
||||
if (rmesh->get_surface_count() <= p_surface_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
Array arrays = rmesh->surface_get_arrays(p_surface_id);
|
||||
|
||||
LocalVector<Vector3> verts = PoolVector<Vector3>(arrays[VS::ARRAY_VERTEX]);
|
||||
if (!verts.size()) {
|
||||
// early out if there are no vertices, no point in doing anything else
|
||||
return;
|
||||
}
|
||||
|
||||
LocalVector<Vector3> normals = PoolVector<Vector3>(arrays[VS::ARRAY_NORMAL]);
|
||||
LocalVector<real_t> tangents = PoolVector<real_t>(arrays[VS::ARRAY_TANGENT]);
|
||||
LocalVector<Color> colors = PoolVector<Color>(arrays[VS::ARRAY_COLOR]);
|
||||
LocalVector<Vector2> uvs = PoolVector<Vector2>(arrays[VS::ARRAY_TEX_UV]);
|
||||
LocalVector<Vector2> uv2s = PoolVector<Vector2>(arrays[VS::ARRAY_TEX_UV2]);
|
||||
LocalVector<int> indices = PoolVector<int>(arrays[VS::ARRAY_INDEX]);
|
||||
|
||||
// The attributes present must match the first mesh for the attributes
|
||||
// to remain in sync. Here we reject meshes with different attributes.
|
||||
// We could alternatively invent missing attributes.
|
||||
// This should hopefully be already caught by the mesh_format, but is included just in case here.
|
||||
|
||||
// Don't perform these checks on the first Mesh, the first Mesh is a master
|
||||
// and determines the attributes we want to be present.
|
||||
if (r_verts.size() != 0) {
|
||||
if ((bool)r_norms.size() != (bool)normals.size()) {
|
||||
ERR_FAIL_MSG("Attribute mismatch with first Mesh (Normals), ignoring surface.");
|
||||
}
|
||||
if ((bool)r_tangents.size() != (bool)tangents.size()) {
|
||||
ERR_FAIL_MSG("Attribute mismatch with first Mesh (Tangents), ignoring surface.");
|
||||
}
|
||||
if ((bool)r_colors.size() != (bool)colors.size()) {
|
||||
ERR_FAIL_MSG("Attribute mismatch with first Mesh (Colors), ignoring surface.");
|
||||
}
|
||||
if ((bool)r_uvs.size() != (bool)uvs.size()) {
|
||||
ERR_FAIL_MSG("Attribute mismatch with first Mesh (UVs), ignoring surface.");
|
||||
}
|
||||
if ((bool)r_uv2s.size() != (bool)uv2s.size()) {
|
||||
ERR_FAIL_MSG("Attribute mismatch with first Mesh (UV2s), ignoring surface.");
|
||||
}
|
||||
}
|
||||
|
||||
// The checking for valid triangles should be on WORLD SPACE vertices,
|
||||
// NOT model space
|
||||
|
||||
// special case, if no indices, create some
|
||||
int num_indices_before = indices.size();
|
||||
if (!_ensure_indices_valid(indices, verts)) {
|
||||
_merge_log("\tignoring INVALID TRIANGLES (duplicate indices or zero area triangle) detected in " + p_mi.get_name() + ", num inds before / after " + itos(num_indices_before) + " / " + itos(indices.size()));
|
||||
}
|
||||
|
||||
// the first index of this mesh is offset from the verts we already have stored in the merged mesh
|
||||
int starting_index = r_verts.size();
|
||||
|
||||
// transform verts to world space
|
||||
Transform tr = p_mi.get_global_transform();
|
||||
|
||||
// But relative to the destination transform.
|
||||
// This can either be identity (when the destination is global space),
|
||||
// or the global transform of the owner MeshInstance (if using local space is selected).
|
||||
tr = p_dest_tr_inv * tr;
|
||||
|
||||
// to transform normals
|
||||
Basis normal_basis = tr.basis.inverse();
|
||||
normal_basis.transpose();
|
||||
|
||||
int num_verts = verts.size();
|
||||
|
||||
// verts
|
||||
DEV_ASSERT(num_verts > 0);
|
||||
int first_vert = r_verts.size();
|
||||
r_verts.resize(first_vert + num_verts);
|
||||
Vector3 *dest_verts = &r_verts[first_vert];
|
||||
|
||||
for (int n = 0; n < num_verts; n++) {
|
||||
Vector3 pt_world = tr.xform(verts[n]);
|
||||
*dest_verts++ = pt_world;
|
||||
}
|
||||
|
||||
// normals
|
||||
if (normals.size()) {
|
||||
int first_norm = r_norms.size();
|
||||
r_norms.resize(first_norm + num_verts);
|
||||
Vector3 *dest_norms = &r_norms[first_norm];
|
||||
for (int n = 0; n < num_verts; n++) {
|
||||
Vector3 pt_norm = normal_basis.xform(normals[n]);
|
||||
pt_norm.normalize();
|
||||
*dest_norms++ = pt_norm;
|
||||
}
|
||||
}
|
||||
|
||||
// tangents
|
||||
if (tangents.size()) {
|
||||
int first_tang = r_tangents.size();
|
||||
r_tangents.resize(first_tang + (num_verts * 4));
|
||||
real_t *dest_tangents = &r_tangents[first_tang];
|
||||
|
||||
for (int n = 0; n < num_verts; n++) {
|
||||
int tstart = n * 4;
|
||||
Vector3 pt_tangent = Vector3(tangents[tstart], tangents[tstart + 1], tangents[tstart + 2]);
|
||||
real_t fourth = tangents[tstart + 3];
|
||||
|
||||
pt_tangent = normal_basis.xform(pt_tangent);
|
||||
pt_tangent.normalize();
|
||||
*dest_tangents++ = pt_tangent.x;
|
||||
*dest_tangents++ = pt_tangent.y;
|
||||
*dest_tangents++ = pt_tangent.z;
|
||||
*dest_tangents++ = fourth;
|
||||
}
|
||||
}
|
||||
|
||||
// colors
|
||||
if (colors.size()) {
|
||||
int first_col = r_colors.size();
|
||||
r_colors.resize(first_col + num_verts);
|
||||
Color *dest_colors = &r_colors[first_col];
|
||||
|
||||
for (int n = 0; n < num_verts; n++) {
|
||||
*dest_colors++ = colors[n];
|
||||
}
|
||||
}
|
||||
|
||||
// uvs
|
||||
if (uvs.size()) {
|
||||
int first_uv = r_uvs.size();
|
||||
r_uvs.resize(first_uv + num_verts);
|
||||
Vector2 *dest_uvs = &r_uvs[first_uv];
|
||||
|
||||
for (int n = 0; n < num_verts; n++) {
|
||||
*dest_uvs++ = uvs[n];
|
||||
}
|
||||
}
|
||||
|
||||
// uv2s
|
||||
if (uv2s.size()) {
|
||||
int first_uv2 = r_uv2s.size();
|
||||
r_uv2s.resize(first_uv2 + num_verts);
|
||||
Vector2 *dest_uv2s = &r_uv2s[first_uv2];
|
||||
|
||||
for (int n = 0; n < num_verts; n++) {
|
||||
*dest_uv2s++ = uv2s[n];
|
||||
}
|
||||
}
|
||||
|
||||
// indices
|
||||
if (indices.size()) {
|
||||
int first_ind = r_inds.size();
|
||||
r_inds.resize(first_ind + indices.size());
|
||||
int *dest_inds = &r_inds[first_ind];
|
||||
|
||||
for (unsigned int n = 0; n < indices.size(); n++) {
|
||||
int ind = indices[n] + starting_index;
|
||||
*dest_inds++ = ind;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool MeshInstance::_ensure_indices_valid(LocalVector<int> &r_indices, const PoolVector<Vector3> &p_verts) const {
|
||||
// no indices? create some
|
||||
if (!r_indices.size()) {
|
||||
_merge_log("\t\t\t\tindices are blank, creating...");
|
||||
|
||||
// indices are blank!! let's create some, assuming the mesh is using triangles
|
||||
r_indices.resize(p_verts.size());
|
||||
|
||||
// this is assuming each triangle vertex is unique
|
||||
for (unsigned int n = 0; n < r_indices.size(); n++) {
|
||||
r_indices[n] = n;
|
||||
}
|
||||
}
|
||||
|
||||
if (!_check_for_valid_indices(r_indices, p_verts, nullptr)) {
|
||||
LocalVector<int> new_inds;
|
||||
_check_for_valid_indices(r_indices, p_verts, &new_inds);
|
||||
|
||||
// copy the new indices
|
||||
r_indices = new_inds;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// check for invalid tris, or make a list of the valid triangles, depending on whether r_inds is set
|
||||
bool MeshInstance::_check_for_valid_indices(const LocalVector<int> &p_inds, const PoolVector<Vector3> &p_verts, LocalVector<int> *r_inds) const {
|
||||
int nTris = p_inds.size();
|
||||
nTris /= 3;
|
||||
int indCount = 0;
|
||||
|
||||
for (int t = 0; t < nTris; t++) {
|
||||
int i0 = p_inds[indCount++];
|
||||
int i1 = p_inds[indCount++];
|
||||
int i2 = p_inds[indCount++];
|
||||
|
||||
bool ok = true;
|
||||
|
||||
// if the indices are the same, the triangle is invalid
|
||||
if (i0 == i1) {
|
||||
ok = false;
|
||||
}
|
||||
if (i1 == i2) {
|
||||
ok = false;
|
||||
}
|
||||
if (i0 == i2) {
|
||||
ok = false;
|
||||
}
|
||||
|
||||
// check positions
|
||||
if (ok) {
|
||||
// vertex positions
|
||||
const Vector3 &p0 = p_verts[i0];
|
||||
const Vector3 &p1 = p_verts[i1];
|
||||
const Vector3 &p2 = p_verts[i2];
|
||||
|
||||
// if the area is zero, the triangle is invalid (and will crash xatlas if we use it)
|
||||
if (_triangle_is_degenerate(p0, p1, p2, 0.00001)) {
|
||||
_merge_log("\t\tdetected zero area triangle, ignoring");
|
||||
ok = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (ok) {
|
||||
// if the triangle is ok, we will output it if we are outputting
|
||||
if (r_inds) {
|
||||
r_inds->push_back(i0);
|
||||
r_inds->push_back(i1);
|
||||
r_inds->push_back(i2);
|
||||
}
|
||||
} else {
|
||||
// if triangle not ok, return failed check if we are not outputting
|
||||
if (!r_inds) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MeshInstance::_triangle_is_degenerate(const Vector3 &p_a, const Vector3 &p_b, const Vector3 &p_c, real_t p_epsilon) const {
|
||||
// not interested in the actual area, but numerical stability
|
||||
Vector3 edge1 = p_b - p_a;
|
||||
Vector3 edge2 = p_c - p_a;
|
||||
|
||||
// for numerical stability keep these values reasonably high
|
||||
edge1 *= 1024.0;
|
||||
edge2 *= 1024.0;
|
||||
|
||||
Vector3 vec = edge1.cross(edge2);
|
||||
real_t sl = vec.length_squared();
|
||||
|
||||
if (sl <= p_epsilon) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// If p_check_compatibility is set to false you MUST have performed a prior check using
|
||||
// is_mergeable_with, otherwise you could get mismatching surface formats leading to graphical errors etc.
|
||||
bool MeshInstance::_merge_meshes(Vector<MeshInstance *> p_list, bool p_use_global_space, bool p_check_compatibility) {
|
||||
if (p_list.size() < 1) {
|
||||
// should not happen but just in case
|
||||
return false;
|
||||
}
|
||||
|
||||
// use the first mesh instance to get common data like number of surfaces
|
||||
const MeshInstance *first = p_list[0];
|
||||
|
||||
// Mesh compatibility checking. This is relatively expensive, so if done already (e.g. in Room system)
|
||||
// this step can be avoided.
|
||||
LocalVector<bool> compat_list;
|
||||
if (p_check_compatibility) {
|
||||
compat_list.resize(p_list.size());
|
||||
|
||||
for (int n = 0; n < p_list.size(); n++) {
|
||||
compat_list[n] = false;
|
||||
}
|
||||
|
||||
compat_list[0] = true;
|
||||
|
||||
for (uint32_t n = 1; n < compat_list.size(); n++) {
|
||||
compat_list[n] = first->_is_mergeable_with(*p_list[n]);
|
||||
|
||||
if (compat_list[n] == false) {
|
||||
WARN_PRINT("MeshInstance " + p_list[n]->get_name() + " is incompatible for merging with " + first->get_name() + ", ignoring.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ref<ArrayMesh> am;
|
||||
am.instance();
|
||||
|
||||
// If we want a local space result, we need the world space transform of this MeshInstance
|
||||
// available to back transform verts from world space.
|
||||
Transform dest_tr_inv;
|
||||
if (!p_use_global_space) {
|
||||
if (is_inside_tree()) {
|
||||
dest_tr_inv = get_global_transform();
|
||||
dest_tr_inv.affine_invert();
|
||||
} else {
|
||||
WARN_PRINT("MeshInstance must be inside tree to merge using local space, falling back to global space.");
|
||||
}
|
||||
}
|
||||
|
||||
for (int s = 0; s < first->get_mesh()->get_surface_count(); s++) {
|
||||
LocalVector<Vector3> verts;
|
||||
LocalVector<Vector3> normals;
|
||||
LocalVector<real_t> tangents;
|
||||
LocalVector<Color> colors;
|
||||
LocalVector<Vector2> uvs;
|
||||
LocalVector<Vector2> uv2s;
|
||||
LocalVector<int> inds;
|
||||
|
||||
for (int n = 0; n < p_list.size(); n++) {
|
||||
// Ignore if the mesh is incompatible
|
||||
if (p_check_compatibility && (!compat_list[n])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
_merge_into_mesh_data(*p_list[n], dest_tr_inv, s, verts, normals, tangents, colors, uvs, uv2s, inds);
|
||||
} // for n through source meshes
|
||||
|
||||
if (!verts.size()) {
|
||||
WARN_PRINT_ONCE("No vertices for surface");
|
||||
}
|
||||
|
||||
// sanity check on the indices
|
||||
for (unsigned int n = 0; n < inds.size(); n++) {
|
||||
int i = inds[n];
|
||||
if ((unsigned int)i >= verts.size()) {
|
||||
WARN_PRINT_ONCE("Mesh index out of range, invalid mesh, aborting");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Array arr;
|
||||
arr.resize(Mesh::ARRAY_MAX);
|
||||
arr[Mesh::ARRAY_VERTEX] = PoolVector<Vector3>(verts);
|
||||
if (normals.size()) {
|
||||
arr[Mesh::ARRAY_NORMAL] = PoolVector<Vector3>(normals);
|
||||
}
|
||||
if (tangents.size()) {
|
||||
arr[Mesh::ARRAY_TANGENT] = PoolVector<real_t>(tangents);
|
||||
}
|
||||
if (colors.size()) {
|
||||
arr[Mesh::ARRAY_COLOR] = PoolVector<Color>(colors);
|
||||
}
|
||||
if (uvs.size()) {
|
||||
arr[Mesh::ARRAY_TEX_UV] = PoolVector<Vector2>(uvs);
|
||||
}
|
||||
if (uv2s.size()) {
|
||||
arr[Mesh::ARRAY_TEX_UV2] = PoolVector<Vector2>(uv2s);
|
||||
}
|
||||
arr[Mesh::ARRAY_INDEX] = PoolVector<int>(inds);
|
||||
|
||||
am->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, arr, Array(), Mesh::ARRAY_COMPRESS_DEFAULT);
|
||||
} // for s through surfaces
|
||||
|
||||
// set all the surfaces on the mesh
|
||||
set_mesh(am);
|
||||
|
||||
// set merged materials
|
||||
int num_surfaces = first->get_mesh()->get_surface_count();
|
||||
for (int n = 0; n < num_surfaces; n++) {
|
||||
set_surface_material(n, first->get_active_material(n));
|
||||
}
|
||||
|
||||
// set some properties to match the merged meshes
|
||||
set_material_overlay(first->get_material_overlay());
|
||||
set_material_override(first->get_material_override());
|
||||
set_cast_shadows_setting(first->get_cast_shadows_setting());
|
||||
set_flag(FLAG_USE_BAKED_LIGHT, first->get_flag(FLAG_USE_BAKED_LIGHT));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MeshInstance::_merge_log(String p_string) const {
|
||||
print_verbose(p_string);
|
||||
}
|
||||
|
||||
void MeshInstance::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("set_mesh", "mesh"), &MeshInstance::set_mesh);
|
||||
ClassDB::bind_method(D_METHOD("get_mesh"), &MeshInstance::get_mesh);
|
||||
@ -1373,8 +963,8 @@ void MeshInstance::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("create_debug_tangents"), &MeshInstance::create_debug_tangents);
|
||||
ClassDB::set_method_flags("MeshInstance", "create_debug_tangents", METHOD_FLAGS_DEFAULT | METHOD_FLAG_EDITOR);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("is_mergeable_with", "other_mesh_instance"), &MeshInstance::is_mergeable_with);
|
||||
ClassDB::bind_method(D_METHOD("merge_meshes", "mesh_instances", "use_global_space", "check_compatibility"), &MeshInstance::merge_meshes, DEFVAL(Vector<Variant>()), DEFVAL(false), DEFVAL(true));
|
||||
ClassDB::bind_method(D_METHOD("is_mergeable_with", "other_mesh_instance", "shadows_only"), &MeshInstance::is_mergeable_with, DEFVAL(false));
|
||||
ClassDB::bind_method(D_METHOD("merge_meshes", "mesh_instances", "use_global_space", "check_compatibility", "shadows_only"), &MeshInstance::merge_meshes, DEFVAL(false), DEFVAL(true), DEFVAL(false));
|
||||
ClassDB::set_method_flags("MeshInstance", "merge_meshes", METHOD_FLAGS_DEFAULT);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "mesh", PROPERTY_HINT_RESOURCE_TYPE, "Mesh"), "set_mesh", "get_mesh");
|
||||
|
@ -41,6 +41,8 @@
|
||||
class MeshInstance : public GeometryInstance {
|
||||
GDCLASS(MeshInstance, GeometryInstance);
|
||||
|
||||
friend class CSGShape;
|
||||
|
||||
protected:
|
||||
Ref<Mesh> mesh;
|
||||
Ref<Skin> skin;
|
||||
@ -94,16 +96,6 @@ protected:
|
||||
void _initialize_skinning(bool p_force_reset = false, bool p_call_attach_skeleton = true);
|
||||
void _update_skinning();
|
||||
|
||||
private:
|
||||
// merging
|
||||
bool _merge_meshes(Vector<MeshInstance *> p_list, bool p_use_global_space, bool p_check_compatibility);
|
||||
bool _is_mergeable_with(const MeshInstance &p_other) const;
|
||||
void _merge_into_mesh_data(const MeshInstance &p_mi, const Transform &p_dest_tr_inv, int p_surface_id, LocalVector<Vector3> &r_verts, LocalVector<Vector3> &r_norms, LocalVector<real_t> &r_tangents, LocalVector<Color> &r_colors, LocalVector<Vector2> &r_uvs, LocalVector<Vector2> &r_uv2s, LocalVector<int> &r_inds);
|
||||
bool _ensure_indices_valid(LocalVector<int> &r_indices, const PoolVector<Vector3> &p_verts) const;
|
||||
bool _check_for_valid_indices(const LocalVector<int> &p_inds, const PoolVector<Vector3> &p_verts, LocalVector<int> *r_inds) const;
|
||||
bool _triangle_is_degenerate(const Vector3 &p_a, const Vector3 &p_b, const Vector3 &p_c, real_t p_epsilon) const;
|
||||
void _merge_log(String p_string) const;
|
||||
|
||||
protected:
|
||||
bool _set(const StringName &p_name, const Variant &p_value);
|
||||
bool _get(const StringName &p_name, Variant &r_ret) const;
|
||||
@ -145,9 +137,10 @@ public:
|
||||
|
||||
void create_debug_tangents();
|
||||
|
||||
// merging
|
||||
bool is_mergeable_with(Node *p_other) const;
|
||||
bool merge_meshes(Vector<Variant> p_list, bool p_use_global_space, bool p_check_compatibility);
|
||||
// Merging.
|
||||
bool is_mergeable_with(Node *p_other, bool p_shadows_only) const;
|
||||
bool merge_meshes(Vector<Variant> p_list, bool p_use_global_space, bool p_check_compatibility, bool p_shadows_only);
|
||||
bool split_by_surface(Vector<Variant> p_destination_mesh_instances);
|
||||
|
||||
virtual AABB get_aabb() const;
|
||||
virtual PoolVector<Face3> get_faces(uint32_t p_usage_flags) const;
|
||||
|
@ -2181,7 +2181,7 @@ void RoomManager::_merge_meshes_in_room(Room *p_room) {
|
||||
if (!bf.get_bit(c)) {
|
||||
MeshInstance *b = source_meshes[c];
|
||||
|
||||
if (a->is_mergeable_with(b)) {
|
||||
if (a->is_mergeable_with(b, false)) {
|
||||
merge_list.push_back(b);
|
||||
bf.set_bit(c, true);
|
||||
}
|
||||
@ -2205,7 +2205,7 @@ void RoomManager::_merge_meshes_in_room(Room *p_room) {
|
||||
variant_merge_list.set(i, merge_list[i]);
|
||||
}
|
||||
|
||||
if (merged->merge_meshes(variant_merge_list, true, false)) {
|
||||
if (merged->merge_meshes(variant_merge_list, true, false, false)) {
|
||||
// set all the source meshes to portal mode ignore so not shown
|
||||
for (int i = 0; i < merge_list.size(); i++) {
|
||||
merge_list[i]->set_portal_mode(CullInstance::PORTAL_MODE_IGNORE);
|
||||
@ -2310,8 +2310,10 @@ void RoomManager::_list_mergeable_mesh_instances(Spatial *p_node, LocalVector<Me
|
||||
// disallow for portals or bounds
|
||||
// mesh instance portals should be queued for deletion by this point, we don't want to merge portals!
|
||||
if (!_node_is_type<Portal>(mi) && !_name_ends_with(mi, "-bound") && !mi->is_queued_for_deletion()) {
|
||||
// only merge if visible
|
||||
if (mi->is_inside_tree() && mi->is_visible()) {
|
||||
// Only merge if visible.
|
||||
// N.B. get_allow_merging() is the old flag on CullInstance, and is maintained for backward compatibility only.
|
||||
// It is overruled by the Spatial "merging_mode" if this is set.
|
||||
if (mi->is_inside_tree() && mi->is_visible() && mi->get_allow_merging()) {
|
||||
r_list.push_back(mi);
|
||||
}
|
||||
}
|
||||
|
@ -72,6 +72,8 @@ future: no idea
|
||||
|
||||
*/
|
||||
|
||||
VARIANT_ENUM_CAST(Spatial::MergingMode);
|
||||
|
||||
SpatialGizmo::SpatialGizmo() {
|
||||
}
|
||||
|
||||
@ -164,6 +166,14 @@ void Spatial::_notification(int p_what) {
|
||||
data.toplevel_active = true;
|
||||
}
|
||||
|
||||
if (data.merging_mode == MERGING_MODE_INHERIT) {
|
||||
bool merging_allowed = true; // Root node default is for merging to be on
|
||||
if (data.parent) {
|
||||
merging_allowed = data.parent->is_merging_allowed();
|
||||
}
|
||||
_propagate_merging_allowed(merging_allowed);
|
||||
}
|
||||
|
||||
data.dirty |= DIRTY_GLOBAL; //global is always dirty upon entering a scene
|
||||
_notify_dirty();
|
||||
|
||||
@ -671,6 +681,35 @@ void Spatial::_propagate_visibility_changed() {
|
||||
}
|
||||
}
|
||||
|
||||
void Spatial::_propagate_merging_allowed(bool p_merging_allowed) {
|
||||
switch (data.merging_mode) {
|
||||
case MERGING_MODE_INHERIT:
|
||||
// Keep the parent p_allow_merging.
|
||||
break;
|
||||
case MERGING_MODE_OFF: {
|
||||
p_merging_allowed = false;
|
||||
} break;
|
||||
case MERGING_MODE_ON: {
|
||||
p_merging_allowed = true;
|
||||
} break;
|
||||
}
|
||||
|
||||
// No change? No need to propagate further.
|
||||
if (data.merging_allowed == p_merging_allowed) {
|
||||
return;
|
||||
}
|
||||
|
||||
data.merging_allowed = p_merging_allowed;
|
||||
|
||||
for (List<Spatial *>::Element *E = data.children.front(); E; E = E->next()) {
|
||||
Spatial *c = E->get();
|
||||
if (!c) {
|
||||
continue;
|
||||
}
|
||||
c->_propagate_merging_allowed(p_merging_allowed);
|
||||
}
|
||||
}
|
||||
|
||||
void Spatial::show() {
|
||||
if (data.visible) {
|
||||
return;
|
||||
@ -850,6 +889,32 @@ bool Spatial::is_local_transform_notification_enabled() const {
|
||||
return data.notify_local_transform;
|
||||
}
|
||||
|
||||
void Spatial::set_merging_mode(MergingMode p_mode) {
|
||||
if (data.merging_mode == p_mode) {
|
||||
return;
|
||||
}
|
||||
|
||||
data.merging_mode = p_mode;
|
||||
|
||||
bool merging_allowed = true; // Default for root node.
|
||||
|
||||
switch (p_mode) {
|
||||
case MERGING_MODE_INHERIT: {
|
||||
if (get_parent_spatial()) {
|
||||
merging_allowed = get_parent_spatial()->is_merging_allowed();
|
||||
}
|
||||
} break;
|
||||
case MERGING_MODE_OFF: {
|
||||
merging_allowed = false;
|
||||
} break;
|
||||
case MERGING_MODE_ON: {
|
||||
merging_allowed = true;
|
||||
} break;
|
||||
}
|
||||
|
||||
_propagate_merging_allowed(merging_allowed);
|
||||
}
|
||||
|
||||
void Spatial::force_update_transform() {
|
||||
ERR_FAIL_COND(!is_inside_tree());
|
||||
if (!xform_change.in_list()) {
|
||||
@ -925,6 +990,9 @@ void Spatial::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("look_at", "target", "up"), &Spatial::look_at);
|
||||
ClassDB::bind_method(D_METHOD("look_at_from_position", "position", "target", "up"), &Spatial::look_at_from_position);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_merging_mode", "mode"), &Spatial::set_merging_mode);
|
||||
ClassDB::bind_method(D_METHOD("get_merging_mode"), &Spatial::get_merging_mode);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("to_local", "global_point"), &Spatial::to_local);
|
||||
ClassDB::bind_method(D_METHOD("to_global", "local_point"), &Spatial::to_global);
|
||||
|
||||
@ -935,6 +1003,10 @@ void Spatial::_bind_methods() {
|
||||
BIND_CONSTANT(NOTIFICATION_ENTER_GAMEPLAY);
|
||||
BIND_CONSTANT(NOTIFICATION_EXIT_GAMEPLAY);
|
||||
|
||||
BIND_ENUM_CONSTANT(MERGING_MODE_INHERIT);
|
||||
BIND_ENUM_CONSTANT(MERGING_MODE_OFF);
|
||||
BIND_ENUM_CONSTANT(MERGING_MODE_ON);
|
||||
|
||||
ADD_GROUP("Transform", "");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "position", PROPERTY_HINT_NONE, "", 0), "set_translation", "get_translation");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "translation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_translation", "get_translation");
|
||||
@ -951,6 +1023,8 @@ void Spatial::_bind_methods() {
|
||||
ADD_GROUP("Visibility", "");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "visible"), "set_visible", "is_visible");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "gizmo", PROPERTY_HINT_RESOURCE_TYPE, "SpatialGizmo", 0), "set_gizmo", "get_gizmo");
|
||||
ADD_GROUP("Misc", "");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "merging_mode", PROPERTY_HINT_ENUM, "Inherit,Off,On"), "set_merging_mode", "get_merging_mode");
|
||||
|
||||
ADD_SIGNAL(MethodInfo("visibility_changed"));
|
||||
ADD_SIGNAL(MethodInfo("gameplay_entered"));
|
||||
@ -971,6 +1045,8 @@ Spatial::Spatial() :
|
||||
data.visible = true;
|
||||
data.disable_scale = false;
|
||||
data.vi_visible = true;
|
||||
data.merging_allowed = true;
|
||||
data.merging_mode = MERGING_MODE_INHERIT;
|
||||
|
||||
data.client_physics_interpolation_data = nullptr;
|
||||
|
||||
|
@ -52,6 +52,14 @@ class Spatial : public Node {
|
||||
GDCLASS(Spatial, Node);
|
||||
OBJ_CATEGORY("3D");
|
||||
|
||||
public:
|
||||
enum MergingMode : unsigned int {
|
||||
MERGING_MODE_INHERIT,
|
||||
MERGING_MODE_OFF,
|
||||
MERGING_MODE_ON
|
||||
};
|
||||
|
||||
private:
|
||||
// optionally stored if we need to do interpolation
|
||||
// client side (i.e. not in VisualServer) so interpolated transforms
|
||||
// can be read back with get_global_transform_interpolated()
|
||||
@ -82,6 +90,8 @@ class Spatial : public Node {
|
||||
|
||||
Viewport *viewport;
|
||||
|
||||
MergingMode merging_mode : 2;
|
||||
|
||||
bool toplevel_active : 1;
|
||||
bool toplevel : 1;
|
||||
bool inside_world : 1;
|
||||
@ -98,6 +108,8 @@ class Spatial : public Node {
|
||||
bool visible : 1;
|
||||
bool disable_scale : 1;
|
||||
|
||||
bool merging_allowed : 1;
|
||||
|
||||
int children_lock;
|
||||
Spatial *parent;
|
||||
List<Spatial *> children;
|
||||
@ -118,6 +130,7 @@ class Spatial : public Node {
|
||||
void _propagate_transform_changed(Spatial *p_origin);
|
||||
|
||||
void _propagate_visibility_changed();
|
||||
void _propagate_merging_allowed(bool p_merging_allowed);
|
||||
|
||||
protected:
|
||||
_FORCE_INLINE_ void set_ignore_transform_notification(bool p_ignore) { data.ignore_notification = p_ignore; }
|
||||
@ -220,6 +233,10 @@ public:
|
||||
void set_notify_local_transform(bool p_enable);
|
||||
bool is_local_transform_notification_enabled() const;
|
||||
|
||||
void set_merging_mode(MergingMode p_mode);
|
||||
MergingMode get_merging_mode() const { return data.merging_mode; }
|
||||
_FORCE_INLINE_ bool is_merging_allowed() const { return data.merging_allowed; }
|
||||
|
||||
void orthonormalize();
|
||||
void set_identity();
|
||||
|
||||
|
@ -253,7 +253,7 @@ void GeometryInstance::set_generate_lightmap(bool p_enabled) {
|
||||
generate_lightmap = p_enabled;
|
||||
}
|
||||
|
||||
bool GeometryInstance::get_generate_lightmap() {
|
||||
bool GeometryInstance::get_generate_lightmap() const {
|
||||
return generate_lightmap;
|
||||
}
|
||||
|
||||
@ -322,9 +322,10 @@ bool GeometryInstance::get_flag(Flags p_flag) const {
|
||||
}
|
||||
|
||||
void GeometryInstance::set_cast_shadows_setting(ShadowCastingSetting p_shadow_casting_setting) {
|
||||
shadow_casting_setting = p_shadow_casting_setting;
|
||||
|
||||
VS::get_singleton()->instance_geometry_set_cast_shadows_setting(get_instance(), (VS::ShadowCastingSetting)p_shadow_casting_setting);
|
||||
if (p_shadow_casting_setting != shadow_casting_setting) {
|
||||
shadow_casting_setting = p_shadow_casting_setting;
|
||||
VS::get_singleton()->instance_geometry_set_cast_shadows_setting(get_instance(), (VS::ShadowCastingSetting)p_shadow_casting_setting);
|
||||
}
|
||||
}
|
||||
|
||||
GeometryInstance::ShadowCastingSetting GeometryInstance::get_cast_shadows_setting() const {
|
||||
@ -333,8 +334,10 @@ GeometryInstance::ShadowCastingSetting GeometryInstance::get_cast_shadows_settin
|
||||
|
||||
void GeometryInstance::set_extra_cull_margin(float p_margin) {
|
||||
ERR_FAIL_COND(p_margin < 0);
|
||||
extra_cull_margin = p_margin;
|
||||
VS::get_singleton()->instance_set_extra_visibility_margin(get_instance(), extra_cull_margin);
|
||||
if (p_margin != extra_cull_margin) {
|
||||
extra_cull_margin = p_margin;
|
||||
VS::get_singleton()->instance_set_extra_visibility_margin(get_instance(), extra_cull_margin);
|
||||
}
|
||||
}
|
||||
|
||||
float GeometryInstance::get_extra_cull_margin() const {
|
||||
|
@ -140,7 +140,7 @@ public:
|
||||
ShadowCastingSetting get_cast_shadows_setting() const;
|
||||
|
||||
void set_generate_lightmap(bool p_enabled);
|
||||
bool get_generate_lightmap();
|
||||
bool get_generate_lightmap() const;
|
||||
|
||||
void set_lightmap_scale(LightmapScale p_scale);
|
||||
LightmapScale get_lightmap_scale() const;
|
||||
|
@ -2193,6 +2193,14 @@ Node *Node::_duplicate(int p_flags, Map<const Node *, Node *> *r_duplimap) const
|
||||
|
||||
bool instanced = false;
|
||||
|
||||
// No need to load a packed scene more than once if features
|
||||
// several times in the branch being duplicated.
|
||||
struct LoadedPackedScene {
|
||||
Ref<PackedScene> scene;
|
||||
String filename;
|
||||
};
|
||||
LocalVector<LoadedPackedScene> loaded_scenes;
|
||||
|
||||
if (Object::cast_to<InstancePlaceholder>(this)) {
|
||||
const InstancePlaceholder *ip = Object::cast_to<const InstancePlaceholder>(this);
|
||||
InstancePlaceholder *nip = memnew(InstancePlaceholder);
|
||||
@ -2200,7 +2208,25 @@ Node *Node::_duplicate(int p_flags, Map<const Node *, Node *> *r_duplimap) const
|
||||
node = nip;
|
||||
|
||||
} else if ((p_flags & DUPLICATE_USE_INSTANCING) && get_filename() != String()) {
|
||||
Ref<PackedScene> res = ResourceLoader::load(get_filename());
|
||||
// already loaded?
|
||||
int found = -1;
|
||||
for (unsigned int n = 0; n < loaded_scenes.size(); n++) {
|
||||
if (loaded_scenes[n].filename == get_filename()) {
|
||||
found = n;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ref<PackedScene> res = Ref<PackedScene>();
|
||||
if (found != -1) {
|
||||
res = loaded_scenes[found].scene;
|
||||
} else {
|
||||
LoadedPackedScene ps;
|
||||
ps.filename = get_filename();
|
||||
ps.scene = ResourceLoader::load(get_filename());
|
||||
res = ps.scene;
|
||||
loaded_scenes.push_back(ps);
|
||||
}
|
||||
|
||||
ERR_FAIL_COND_V(res.is_null(), nullptr);
|
||||
PackedScene::GenEditState ges = PackedScene::GEN_EDIT_STATE_DISABLED;
|
||||
#ifdef TOOLS_ENABLED
|
||||
|
@ -194,6 +194,7 @@
|
||||
#include "scene/3d/label_3d.h"
|
||||
#include "scene/3d/light.h"
|
||||
#include "scene/3d/listener.h"
|
||||
#include "scene/3d/merge_group.h"
|
||||
#include "scene/3d/mesh_instance.h"
|
||||
#include "scene/3d/multimesh_instance.h"
|
||||
#include "scene/3d/navigation.h"
|
||||
@ -465,6 +466,7 @@ void register_scene_types() {
|
||||
ClassDB::register_class<RoomManager>();
|
||||
ClassDB::register_class<Occluder>();
|
||||
ClassDB::register_class<Portal>();
|
||||
ClassDB::register_class<MergeGroup>();
|
||||
|
||||
ClassDB::register_class<RootMotionView>();
|
||||
ClassDB::set_class_enabled("RootMotionView", false); //disabled by default, enabled by editor
|
||||
|
1114
scene/resources/merging_tool.cpp
Normal file
1114
scene/resources/merging_tool.cpp
Normal file
File diff suppressed because it is too large
Load Diff
114
scene/resources/merging_tool.h
Normal file
114
scene/resources/merging_tool.h
Normal file
@ -0,0 +1,114 @@
|
||||
/**************************************************************************/
|
||||
/* merging_tool.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef MERGING_TOOL_H
|
||||
#define MERGING_TOOL_H
|
||||
|
||||
#include "core/local_vector.h"
|
||||
#include "core/vector.h"
|
||||
#include "scene/resources/mesh.h"
|
||||
|
||||
class MeshInstance;
|
||||
class GeometryInstance;
|
||||
class CSGShape;
|
||||
class SurfaceTool;
|
||||
struct CSGBrush;
|
||||
|
||||
#ifdef DEV_ENABLED
|
||||
// Only enable this for development testing.
|
||||
// #define GODOT_MERGING_VERBOSE
|
||||
#endif
|
||||
|
||||
// NOTE : These merging and joining functions DO NOT move children, or delete source nodes. That is the responsibility of the caller.
|
||||
class MergingTool {
|
||||
public:
|
||||
// Are two mesh instances mergeable with each other?
|
||||
static bool is_mergeable_with(const MeshInstance &p_mi, const MeshInstance &p_other, bool p_check_surface_material_match);
|
||||
static bool is_shadow_mergeable_with(const MeshInstance &p_mi, const MeshInstance &p_other);
|
||||
|
||||
// Merges all mesh details.
|
||||
static bool merge_meshes(MeshInstance &r_dest_mi, Vector<MeshInstance *> p_list, bool p_use_global_space, bool p_check_compatibility);
|
||||
|
||||
// Join all surfaces into one ubermesh.
|
||||
static bool join_meshes(MeshInstance &r_dest_mi, Vector<MeshInstance *> p_list);
|
||||
|
||||
// Adds a surface from one mesh to another.
|
||||
static bool join_mesh_surface(const MeshInstance &p_source_mi, uint32_t p_source_surface_id, MeshInstance &r_dest_mi);
|
||||
|
||||
// Only concerned with data necessary for shadow proxy - opaque tris, no normals / tangents / uvs etc.
|
||||
static bool merge_shadow_meshes(MeshInstance &r_dest_mi, Vector<MeshInstance *> p_list, bool p_use_global_space, bool p_check_compatibility);
|
||||
|
||||
// For splitting a MeshInstance with multiple surfaces to a MeshInstance per surface.
|
||||
static bool split_surface_to_mesh_instance(const MeshInstance &p_source_mi, int p_surface_id, MeshInstance &r_mi);
|
||||
|
||||
// Convert a CSG surface to MeshInstance.
|
||||
static bool split_csg_surface_to_mesh_instance(const CSGShape &p_shape, MeshInstance &r_mi, const Ref<ArrayMesh> &p_array_mesh, CSGBrush *p_brush, int p_surface);
|
||||
|
||||
// Remove degenerate triangles.
|
||||
static bool clean_mesh_instance(MeshInstance &p_mi);
|
||||
|
||||
static void split_mesh_instance_by_locality(MeshInstance &r_mi, const AABB &p_bound, uint32_t p_splits_horz, uint32_t p_splits_vert, uint32_t p_min_split_poly_count);
|
||||
|
||||
// For debugging purposes.
|
||||
static void debug_mesh_instance(const MeshInstance &p_mi);
|
||||
#ifdef DEV_ENABLED
|
||||
static void debug_branch(Node *p_node, const char *p_title = nullptr, int p_depth = 0);
|
||||
#endif
|
||||
#ifdef TOOLS_ENABLED
|
||||
static void append_editor_description(Node *p_node, String p_string, Node *p_node_named = nullptr);
|
||||
#endif
|
||||
|
||||
// Helper functions (used from MergeGroup).
|
||||
static void _set_owner_logged(Node *p_node, Node *p_owner);
|
||||
static void _reparent(Node *p_branch, Node *p_new_parent, Node *p_new_owner);
|
||||
static void _invalidate_owner_recursive(Node *p_node, Node *p_old_owner, Node *p_new_owner);
|
||||
static bool _node_has_valid_children(Node *p_node);
|
||||
static void _mesh_set_storage_mode(Mesh *p_mesh, Mesh::StorageMode p_mode);
|
||||
|
||||
private:
|
||||
static void _reparent_subscene_send_new_owner(Node *p_node, Node *p_new_owner);
|
||||
|
||||
static void _copy_mesh_instance_settings(const MeshInstance &p_source, MeshInstance &r_dest, bool p_copy_transform, bool p_copy_materials);
|
||||
static bool _is_mergeable_with_common(const MeshInstance &p_mi, const MeshInstance &p_other);
|
||||
static bool _is_shadow_mergeable(const MeshInstance &p_mi);
|
||||
static bool _is_material_opaque(const Ref<Material> &p_mat);
|
||||
static bool _ensure_indices_valid(LocalVector<int> &r_indices, const PoolVector<Vector3> &p_verts);
|
||||
static bool _check_for_valid_indices(const LocalVector<int> &p_inds, const PoolVector<Vector3> &p_verts, LocalVector<int> *r_inds);
|
||||
static bool _triangle_is_degenerate(const Vector3 &p_a, const Vector3 &p_b, const Vector3 &p_c, real_t p_epsilon);
|
||||
static int _clean_mesh_surface(const String &p_source_name, const Transform &p_xform, Ref<Mesh> &p_rmesh, int p_surface_id, Ref<ArrayMesh> r_dest_mesh);
|
||||
static void _copy_geometry_instance_settings(const GeometryInstance &p_source, MeshInstance &r_dest, bool p_copy_transform);
|
||||
static void _set_rmesh_material(MeshInstance &r_mi, Ref<Mesh> r_rmesh, int p_surface_id, Ref<Material> p_material);
|
||||
|
||||
static void _split_mesh_instance_by_locality(const SurfaceTool &p_st_main, const MeshInstance &p_source_mi, const LocalVector<uint32_t> &p_tri_ids, uint32_t p_local_id, uint32_t p_surface_id, uint32_t p_x, uint32_t p_y, uint32_t p_z);
|
||||
|
||||
static void _merge_log(String p_string, int p_priority = 1);
|
||||
};
|
||||
|
||||
#endif // MERGING_TOOL_H
|
@ -42,32 +42,116 @@
|
||||
|
||||
Mesh::ConvexDecompositionFunc Mesh::convex_decomposition_function = nullptr;
|
||||
|
||||
int Mesh::surface_get_face_count(int p_idx) const {
|
||||
ERR_FAIL_INDEX_V(p_idx, get_surface_count(), 0);
|
||||
|
||||
switch (surface_get_primitive_type(p_idx)) {
|
||||
case PRIMITIVE_TRIANGLES: {
|
||||
int len = (surface_get_format(p_idx) & ARRAY_FORMAT_INDEX) ? surface_get_array_index_len(p_idx) : surface_get_array_len(p_idx);
|
||||
// Don't error if zero, it's valid (we'll just skip it later).
|
||||
ERR_FAIL_COND_V_MSG((len % 3) != 0, 0, vformat("Ignoring surface %d, incorrect %s count: %d (for PRIMITIVE_TRIANGLES).", p_idx, (surface_get_format(p_idx) & ARRAY_FORMAT_INDEX) ? "index" : "vertex", len));
|
||||
return len;
|
||||
} break;
|
||||
case PRIMITIVE_TRIANGLE_FAN:
|
||||
case PRIMITIVE_TRIANGLE_STRIP: {
|
||||
int len = (surface_get_format(p_idx) & ARRAY_FORMAT_INDEX) ? surface_get_array_index_len(p_idx) : surface_get_array_len(p_idx);
|
||||
// Don't error if zero, it's valid (we'll just skip it later).
|
||||
ERR_FAIL_COND_V_MSG(len != 0 && len < 3, 0, vformat("Ignoring surface %d, incorrect %s count: %d (for %s).", p_idx, (surface_get_format(p_idx) & ARRAY_FORMAT_INDEX) ? "index" : "vertex", len, (surface_get_primitive_type(p_idx) == PRIMITIVE_TRIANGLE_FAN) ? "PRIMITIVE_TRIANGLE_FAN" : "PRIMITIVE_TRIANGLE_STRIP"));
|
||||
return (len == 0) ? 0 : (len - 2) * 3;
|
||||
} break;
|
||||
default: {
|
||||
} break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Mesh::get_face_count() const {
|
||||
int faces_size = 0;
|
||||
|
||||
for (int i = 0; i < get_surface_count(); i++) {
|
||||
faces_size += surface_get_face_count(i);
|
||||
}
|
||||
|
||||
return faces_size;
|
||||
}
|
||||
|
||||
Ref<TriangleMesh> Mesh::generate_triangle_mesh_from_aabb() const {
|
||||
AABB aabb = get_aabb();
|
||||
|
||||
Vector3 pts[8];
|
||||
Vector3 s = aabb.position;
|
||||
Vector3 l = s + aabb.size;
|
||||
|
||||
pts[0] = Vector3(s.x, s.y, s.z);
|
||||
pts[1] = Vector3(l.x, s.y, s.z);
|
||||
pts[2] = Vector3(l.x, l.y, s.z);
|
||||
pts[3] = Vector3(s.x, l.y, s.z);
|
||||
pts[4] = Vector3(l.x, l.y, l.z);
|
||||
pts[5] = Vector3(s.x, l.y, l.z);
|
||||
pts[6] = Vector3(s.x, s.y, l.z);
|
||||
pts[7] = Vector3(l.x, s.y, l.z);
|
||||
|
||||
PoolVector<Vector3> face_pts;
|
||||
face_pts.resize(6 * 2 * 3);
|
||||
PoolVector<Vector3>::Write w = face_pts.write();
|
||||
int wc = 0;
|
||||
w[wc++] = pts[0];
|
||||
w[wc++] = pts[1];
|
||||
w[wc++] = pts[2];
|
||||
w[wc++] = pts[0];
|
||||
w[wc++] = pts[2];
|
||||
w[wc++] = pts[3];
|
||||
|
||||
w[wc++] = pts[6];
|
||||
w[wc++] = pts[5];
|
||||
w[wc++] = pts[4];
|
||||
w[wc++] = pts[6];
|
||||
w[wc++] = pts[4];
|
||||
w[wc++] = pts[7];
|
||||
|
||||
w[wc++] = pts[1];
|
||||
w[wc++] = pts[7];
|
||||
w[wc++] = pts[4];
|
||||
w[wc++] = pts[1];
|
||||
w[wc++] = pts[4];
|
||||
w[wc++] = pts[2];
|
||||
|
||||
w[wc++] = pts[0];
|
||||
w[wc++] = pts[3];
|
||||
w[wc++] = pts[5];
|
||||
w[wc++] = pts[0];
|
||||
w[wc++] = pts[5];
|
||||
w[wc++] = pts[6];
|
||||
|
||||
w[wc++] = pts[0];
|
||||
w[wc++] = pts[6];
|
||||
w[wc++] = pts[7];
|
||||
w[wc++] = pts[0];
|
||||
w[wc++] = pts[7];
|
||||
w[wc++] = pts[1];
|
||||
|
||||
w[wc++] = pts[2];
|
||||
w[wc++] = pts[4];
|
||||
w[wc++] = pts[5];
|
||||
w[wc++] = pts[2];
|
||||
w[wc++] = pts[5];
|
||||
w[wc++] = pts[3];
|
||||
|
||||
w.release();
|
||||
|
||||
Ref<TriangleMesh> tmesh = Ref<TriangleMesh>(memnew(TriangleMesh));
|
||||
tmesh->create(face_pts);
|
||||
|
||||
return tmesh;
|
||||
}
|
||||
|
||||
Ref<TriangleMesh> Mesh::generate_triangle_mesh() const {
|
||||
if (triangle_mesh.is_valid()) {
|
||||
return triangle_mesh;
|
||||
}
|
||||
|
||||
int faces_size = 0;
|
||||
|
||||
for (int i = 0; i < get_surface_count(); i++) {
|
||||
switch (surface_get_primitive_type(i)) {
|
||||
case PRIMITIVE_TRIANGLES: {
|
||||
int len = (surface_get_format(i) & ARRAY_FORMAT_INDEX) ? surface_get_array_index_len(i) : surface_get_array_len(i);
|
||||
// Don't error if zero, it's valid (we'll just skip it later).
|
||||
ERR_CONTINUE_MSG((len % 3) != 0, vformat("Ignoring surface %d, incorrect %s count: %d (for PRIMITIVE_TRIANGLES).", i, (surface_get_format(i) & ARRAY_FORMAT_INDEX) ? "index" : "vertex", len));
|
||||
faces_size += len;
|
||||
} break;
|
||||
case PRIMITIVE_TRIANGLE_FAN:
|
||||
case PRIMITIVE_TRIANGLE_STRIP: {
|
||||
int len = (surface_get_format(i) & ARRAY_FORMAT_INDEX) ? surface_get_array_index_len(i) : surface_get_array_len(i);
|
||||
// Don't error if zero, it's valid (we'll just skip it later).
|
||||
ERR_CONTINUE_MSG(len != 0 && len < 3, vformat("Ignoring surface %d, incorrect %s count: %d (for %s).", i, (surface_get_format(i) & ARRAY_FORMAT_INDEX) ? "index" : "vertex", len, (surface_get_primitive_type(i) == PRIMITIVE_TRIANGLE_FAN) ? "PRIMITIVE_TRIANGLE_FAN" : "PRIMITIVE_TRIANGLE_STRIP"));
|
||||
faces_size += (len == 0) ? 0 : (len - 2) * 3;
|
||||
} break;
|
||||
default: {
|
||||
} break;
|
||||
}
|
||||
}
|
||||
int faces_size = get_face_count();
|
||||
|
||||
if (faces_size == 0) {
|
||||
return triangle_mesh;
|
||||
@ -534,6 +618,9 @@ void Mesh::_bind_methods() {
|
||||
BIND_ENUM_CONSTANT(ARRAY_MAX);
|
||||
}
|
||||
|
||||
void Mesh::set_storage_mode(StorageMode p_storage_mode) {
|
||||
}
|
||||
|
||||
void Mesh::clear_cache() const {
|
||||
triangle_mesh.unref();
|
||||
debug_lines.clear();
|
||||
@ -696,6 +783,9 @@ bool ArrayMesh::_get(const StringName &p_name, Variant &r_ret) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Data must be in GPU for this routine to work.
|
||||
ERR_FAIL_COND_V(!_on_gpu, false);
|
||||
|
||||
String sname = p_name;
|
||||
|
||||
if (p_name == "blend_shape/names") {
|
||||
@ -807,18 +897,67 @@ void ArrayMesh::add_surface(uint32_t p_format, PrimitiveType p_primitive, const
|
||||
Surface s;
|
||||
s.aabb = p_aabb;
|
||||
s.is_2d = p_format & ARRAY_FLAG_USE_2D_VERTICES;
|
||||
s.creation_format = p_format;
|
||||
surfaces.push_back(s);
|
||||
_recompute_aabb();
|
||||
|
||||
VisualServer::get_singleton()->mesh_add_surface(mesh, p_format, (VS::PrimitiveType)p_primitive, p_array, p_vertex_count, p_index_array, p_index_count, p_aabb, p_blend_shapes, p_bone_aabbs);
|
||||
}
|
||||
|
||||
void ArrayMesh::clear_cpu_surfaces() {
|
||||
for (unsigned int n = 0; n < _cpu_surfaces.size(); n++) {
|
||||
CPUSurface *s = _cpu_surfaces[n];
|
||||
DEV_ASSERT(s);
|
||||
memdelete(s);
|
||||
}
|
||||
|
||||
_cpu_surfaces.clear();
|
||||
}
|
||||
|
||||
void ArrayMesh::add_surface_from_arrays_cpu_with_probe(PrimitiveType p_primitive, const Array &p_arrays, const Array &p_blend_shapes, uint32_t p_flags, int p_surface_id) {
|
||||
uint32_t creation_format = 0;
|
||||
|
||||
if (_on_gpu) {
|
||||
// query the last created surface format
|
||||
creation_format = VisualServer::get_singleton()->mesh_surface_get_format(mesh, surfaces.size());
|
||||
} else {
|
||||
creation_format = VisualServer::get_singleton()->mesh_find_format_from_arrays((VS::PrimitiveType)p_primitive, p_arrays, p_blend_shapes, p_flags);
|
||||
}
|
||||
|
||||
Surface s = surfaces[p_surface_id];
|
||||
s.creation_flags = p_flags;
|
||||
s.creation_format = creation_format;
|
||||
surfaces.set(p_surface_id, s);
|
||||
|
||||
add_surface_from_arrays_cpu(p_primitive, p_arrays, p_blend_shapes);
|
||||
}
|
||||
|
||||
void ArrayMesh::add_surface_from_arrays_cpu(PrimitiveType p_primitive, const Array &p_arrays, const Array &p_blend_shapes) {
|
||||
CPUSurface *s = memnew(CPUSurface);
|
||||
_cpu_surfaces.push_back(s);
|
||||
|
||||
s->primitive_type = p_primitive;
|
||||
s->arrays = p_arrays;
|
||||
s->blend_shapes = p_blend_shapes;
|
||||
|
||||
if (p_arrays.size() > VS::ARRAY_VERTEX) {
|
||||
// This is horrible but VisualServer uses this .. it may do a conversion to PoolVector3Array?
|
||||
// Maybe this rarely happens.
|
||||
s->num_verts = PoolVector3Array(p_arrays[VS::ARRAY_VERTEX]).size();
|
||||
}
|
||||
if (p_arrays.size() > VS::ARRAY_INDEX) {
|
||||
s->num_inds = PoolIntArray(p_arrays[VS::ARRAY_INDEX]).size();
|
||||
}
|
||||
}
|
||||
|
||||
void ArrayMesh::add_surface_from_arrays(PrimitiveType p_primitive, const Array &p_arrays, const Array &p_blend_shapes, uint32_t p_flags) {
|
||||
ERR_FAIL_COND(p_arrays.size() != ARRAY_MAX);
|
||||
|
||||
Surface s;
|
||||
|
||||
VisualServer::get_singleton()->mesh_add_surface_from_arrays(mesh, (VisualServer::PrimitiveType)p_primitive, p_arrays, p_blend_shapes, p_flags);
|
||||
if (_on_gpu) {
|
||||
VisualServer::get_singleton()->mesh_add_surface_from_arrays(mesh, (VisualServer::PrimitiveType)p_primitive, p_arrays, p_blend_shapes, p_flags);
|
||||
}
|
||||
|
||||
/* make aABB? */ {
|
||||
Variant arr = p_arrays[ARRAY_VERTEX];
|
||||
@ -840,11 +979,16 @@ void ArrayMesh::add_surface_from_arrays(PrimitiveType p_primitive, const Array &
|
||||
|
||||
s.aabb = aabb;
|
||||
s.is_2d = arr.get_type() == Variant::POOL_VECTOR2_ARRAY;
|
||||
s.creation_flags = p_flags;
|
||||
surfaces.push_back(s);
|
||||
|
||||
_recompute_aabb();
|
||||
}
|
||||
|
||||
if (_on_cpu) {
|
||||
add_surface_from_arrays_cpu_with_probe(p_primitive, p_arrays, p_blend_shapes, p_flags, surfaces.size() - 1);
|
||||
}
|
||||
|
||||
clear_cache();
|
||||
_change_notify();
|
||||
emit_changed();
|
||||
@ -852,10 +996,20 @@ void ArrayMesh::add_surface_from_arrays(PrimitiveType p_primitive, const Array &
|
||||
|
||||
Array ArrayMesh::surface_get_arrays(int p_surface) const {
|
||||
ERR_FAIL_INDEX_V(p_surface, surfaces.size(), Array());
|
||||
|
||||
// preferentially read from CPU as quicker
|
||||
if (on_cpu()) {
|
||||
return _cpu_surfaces[p_surface]->arrays;
|
||||
}
|
||||
return VisualServer::get_singleton()->mesh_surface_get_arrays(mesh, p_surface);
|
||||
}
|
||||
Array ArrayMesh::surface_get_blend_shape_arrays(int p_surface) const {
|
||||
ERR_FAIL_INDEX_V(p_surface, surfaces.size(), Array());
|
||||
|
||||
// preferentially read from CPU as quicker
|
||||
if (on_cpu()) {
|
||||
return _cpu_surfaces[p_surface]->blend_shapes;
|
||||
}
|
||||
return VisualServer::get_singleton()->mesh_surface_get_blend_shape_arrays(mesh, p_surface);
|
||||
}
|
||||
|
||||
@ -924,6 +1078,13 @@ void ArrayMesh::surface_remove(int p_idx) {
|
||||
VisualServer::get_singleton()->mesh_remove_surface(mesh, p_idx);
|
||||
surfaces.remove(p_idx);
|
||||
|
||||
if (on_cpu()) {
|
||||
CPUSurface *s = _cpu_surfaces[p_idx];
|
||||
DEV_ASSERT(s);
|
||||
memdelete(s);
|
||||
_cpu_surfaces.remove(p_idx);
|
||||
}
|
||||
|
||||
clear_cache();
|
||||
_recompute_aabb();
|
||||
_change_notify();
|
||||
@ -932,21 +1093,46 @@ void ArrayMesh::surface_remove(int p_idx) {
|
||||
|
||||
int ArrayMesh::surface_get_array_len(int p_idx) const {
|
||||
ERR_FAIL_INDEX_V(p_idx, surfaces.size(), -1);
|
||||
|
||||
if (on_cpu()) {
|
||||
CPUSurface *s = _cpu_surfaces[p_idx];
|
||||
DEV_ASSERT(s);
|
||||
return s->num_verts;
|
||||
}
|
||||
|
||||
return VisualServer::get_singleton()->mesh_surface_get_array_len(mesh, p_idx);
|
||||
}
|
||||
|
||||
int ArrayMesh::surface_get_array_index_len(int p_idx) const {
|
||||
ERR_FAIL_INDEX_V(p_idx, surfaces.size(), -1);
|
||||
|
||||
if (on_cpu()) {
|
||||
CPUSurface *s = _cpu_surfaces[p_idx];
|
||||
DEV_ASSERT(s);
|
||||
return s->num_inds;
|
||||
}
|
||||
return VisualServer::get_singleton()->mesh_surface_get_array_index_len(mesh, p_idx);
|
||||
}
|
||||
|
||||
uint32_t ArrayMesh::surface_get_format(int p_idx) const {
|
||||
ERR_FAIL_INDEX_V(p_idx, surfaces.size(), 0);
|
||||
|
||||
// not sure whether we need to support this yet?
|
||||
if (!_on_gpu) {
|
||||
return surfaces[p_idx].creation_format;
|
||||
}
|
||||
|
||||
return VisualServer::get_singleton()->mesh_surface_get_format(mesh, p_idx);
|
||||
}
|
||||
|
||||
ArrayMesh::PrimitiveType ArrayMesh::surface_get_primitive_type(int p_idx) const {
|
||||
ERR_FAIL_INDEX_V(p_idx, surfaces.size(), PRIMITIVE_LINES);
|
||||
|
||||
if (on_cpu()) {
|
||||
CPUSurface *s = _cpu_surfaces[p_idx];
|
||||
DEV_ASSERT(s);
|
||||
return s->primitive_type;
|
||||
}
|
||||
return (PrimitiveType)VisualServer::get_singleton()->mesh_surface_get_primitive_type(mesh, p_idx);
|
||||
}
|
||||
|
||||
@ -956,7 +1142,10 @@ void ArrayMesh::surface_set_material(int p_idx, const Ref<Material> &p_material)
|
||||
return;
|
||||
}
|
||||
surfaces.write[p_idx].material = p_material;
|
||||
VisualServer::get_singleton()->mesh_surface_set_material(mesh, p_idx, p_material.is_null() ? RID() : p_material->get_rid());
|
||||
|
||||
if (_on_gpu) {
|
||||
VisualServer::get_singleton()->mesh_surface_set_material(mesh, p_idx, p_material.is_null() ? RID() : p_material->get_rid());
|
||||
}
|
||||
|
||||
_change_notify("material");
|
||||
emit_changed();
|
||||
@ -1040,6 +1229,11 @@ void ArrayMesh::clear_surfaces() {
|
||||
if (!mesh.is_valid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_on_cpu) {
|
||||
clear_cpu_surfaces();
|
||||
}
|
||||
|
||||
VS::get_singleton()->mesh_clear(mesh);
|
||||
surfaces.clear();
|
||||
aabb = AABB();
|
||||
@ -1486,6 +1680,80 @@ void ArrayMesh::reload_from_file() {
|
||||
_change_notify();
|
||||
}
|
||||
|
||||
void ArrayMesh::set_storage_mode(StorageMode p_storage_mode) {
|
||||
if (_storage_mode == p_storage_mode) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool new_on_cpu = false;
|
||||
bool new_on_gpu = false;
|
||||
|
||||
switch (p_storage_mode) {
|
||||
default: {
|
||||
new_on_cpu = false;
|
||||
new_on_gpu = true;
|
||||
} break;
|
||||
case STORAGE_MODE_CPU: {
|
||||
new_on_cpu = true;
|
||||
new_on_gpu = false;
|
||||
} break;
|
||||
case STORAGE_MODE_CPU_AND_GPU: {
|
||||
new_on_cpu = true;
|
||||
new_on_gpu = true;
|
||||
} break;
|
||||
}
|
||||
|
||||
// cpu to gpu?
|
||||
if (new_on_gpu && !_on_gpu) {
|
||||
// must be on cpu to go to gpu
|
||||
DEV_CHECK(_on_cpu);
|
||||
if (mesh.is_valid()) {
|
||||
// make sure mesh is clear (may not be necessary)
|
||||
VS::get_singleton()->mesh_clear(mesh);
|
||||
|
||||
for (unsigned int n = 0; n < _cpu_surfaces.size(); n++) {
|
||||
CPUSurface *s = _cpu_surfaces[n];
|
||||
DEV_ASSERT(s);
|
||||
VisualServer::get_singleton()->mesh_add_surface_from_arrays(mesh, (VisualServer::PrimitiveType)s->primitive_type, s->arrays, s->blend_shapes, surfaces[n].creation_flags);
|
||||
|
||||
ERR_CONTINUE((int)n >= surfaces.size());
|
||||
const Ref<Material> &mat = surfaces[n].material;
|
||||
VisualServer::get_singleton()->mesh_surface_set_material(mesh, n, mat.is_null() ? RID() : mat->get_rid());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// gpu to cpu?
|
||||
if (new_on_cpu && !_on_cpu) {
|
||||
// must be on gpu to go to cpu
|
||||
DEV_CHECK(_on_gpu);
|
||||
clear_cpu_surfaces();
|
||||
|
||||
if (mesh.is_valid()) {
|
||||
for (int n = 0; n < surfaces.size(); n++) {
|
||||
Array arrays = VisualServer::get_singleton()->mesh_surface_get_arrays(mesh, n);
|
||||
Array blend_shapes = VisualServer::get_singleton()->mesh_surface_get_blend_shape_arrays(mesh, n);
|
||||
PrimitiveType primitive = (PrimitiveType)VisualServer::get_singleton()->mesh_surface_get_primitive_type(mesh, n);
|
||||
add_surface_from_arrays_cpu(primitive, arrays, blend_shapes);
|
||||
}
|
||||
} // mesh valid
|
||||
}
|
||||
|
||||
// clear anything not used
|
||||
if (!new_on_cpu) {
|
||||
clear_cpu_surfaces();
|
||||
}
|
||||
if (!new_on_gpu && _on_gpu) {
|
||||
if (mesh.is_valid()) {
|
||||
VS::get_singleton()->mesh_clear(mesh);
|
||||
}
|
||||
}
|
||||
|
||||
_on_cpu = new_on_cpu;
|
||||
_on_gpu = new_on_gpu;
|
||||
_storage_mode = p_storage_mode;
|
||||
}
|
||||
|
||||
ArrayMesh::ArrayMesh() {
|
||||
mesh = RID_PRIME(VisualServer::get_singleton()->mesh_create());
|
||||
blend_shape_mode = BLEND_SHAPE_MODE_RELATIVE;
|
||||
@ -1493,4 +1761,5 @@ ArrayMesh::ArrayMesh() {
|
||||
|
||||
ArrayMesh::~ArrayMesh() {
|
||||
VisualServer::get_singleton()->free(mesh);
|
||||
clear_cpu_surfaces();
|
||||
}
|
||||
|
@ -31,6 +31,7 @@
|
||||
#ifndef MESH_H
|
||||
#define MESH_H
|
||||
|
||||
#include "core/local_vector.h"
|
||||
#include "core/math/face3.h"
|
||||
#include "core/math/triangle_mesh.h"
|
||||
#include "core/resource.h"
|
||||
@ -118,6 +119,12 @@ public:
|
||||
BLEND_SHAPE_MODE_RELATIVE = VS::BLEND_SHAPE_MODE_RELATIVE,
|
||||
};
|
||||
|
||||
enum StorageMode {
|
||||
STORAGE_MODE_GPU,
|
||||
STORAGE_MODE_CPU,
|
||||
STORAGE_MODE_CPU_AND_GPU,
|
||||
};
|
||||
|
||||
virtual int get_surface_count() const = 0;
|
||||
virtual int surface_get_array_len(int p_idx) const = 0;
|
||||
virtual int surface_get_array_index_len(int p_idx) const = 0;
|
||||
@ -129,11 +136,14 @@ public:
|
||||
virtual void surface_set_material(int p_idx, const Ref<Material> &p_material) = 0;
|
||||
virtual Ref<Material> surface_get_material(int p_idx) const = 0;
|
||||
virtual int get_blend_shape_count() const = 0;
|
||||
int surface_get_face_count(int p_idx) const;
|
||||
virtual StringName get_blend_shape_name(int p_index) const = 0;
|
||||
virtual void set_blend_shape_name(int p_index, const StringName &p_name) = 0;
|
||||
|
||||
int get_face_count() const;
|
||||
PoolVector<Face3> get_faces() const;
|
||||
Ref<TriangleMesh> generate_triangle_mesh() const;
|
||||
Ref<TriangleMesh> generate_triangle_mesh_from_aabb() const;
|
||||
void generate_debug_mesh_lines(Vector<Vector3> &r_lines);
|
||||
void generate_debug_mesh_indices(Vector<Vector3> &r_points);
|
||||
|
||||
@ -143,6 +153,7 @@ public:
|
||||
Ref<Mesh> create_outline(float p_margin) const;
|
||||
|
||||
virtual AABB get_aabb() const = 0;
|
||||
virtual void set_storage_mode(StorageMode p_storage_mode);
|
||||
|
||||
void set_lightmap_size_hint(const Vector2 &p_size);
|
||||
Size2 get_lightmap_size_hint() const;
|
||||
@ -162,13 +173,30 @@ class ArrayMesh : public Mesh {
|
||||
RES_BASE_EXTENSION("mesh");
|
||||
|
||||
private:
|
||||
// Storing the mesh data on CPU
|
||||
struct CPUSurface {
|
||||
Array arrays;
|
||||
Array blend_shapes;
|
||||
PrimitiveType primitive_type;
|
||||
int num_verts = 0;
|
||||
int num_inds = 0;
|
||||
};
|
||||
|
||||
struct Surface {
|
||||
String name;
|
||||
AABB aabb;
|
||||
Ref<Material> material;
|
||||
bool is_2d;
|
||||
// Watch for bugs here.
|
||||
// When calling add_surface() rather than add_surface_from_arrays(),
|
||||
// the creation flags will be unset, and left at default.
|
||||
// Conversion from CPU to GPU memory assumes that creation_flags are
|
||||
// correct, so is only TRULY safe when used with add_surface_from_arrays().
|
||||
uint32_t creation_flags = ARRAY_COMPRESS_DEFAULT;
|
||||
uint32_t creation_format = 0;
|
||||
};
|
||||
Vector<Surface> surfaces;
|
||||
LocalVector<CPUSurface *> _cpu_surfaces;
|
||||
RID mesh;
|
||||
AABB aabb;
|
||||
BlendShapeMode blend_shape_mode;
|
||||
@ -177,6 +205,18 @@ private:
|
||||
|
||||
void _recompute_aabb();
|
||||
|
||||
// Data can be held on GPU, CPU or both.
|
||||
// CPU is quicker for modifications, but can't be
|
||||
// used for rendering.
|
||||
bool _on_cpu = false;
|
||||
bool _on_gpu = true;
|
||||
StorageMode _storage_mode = STORAGE_MODE_GPU;
|
||||
|
||||
void add_surface_from_arrays_cpu(PrimitiveType p_primitive, const Array &p_arrays, const Array &p_blend_shapes);
|
||||
void add_surface_from_arrays_cpu_with_probe(PrimitiveType p_primitive, const Array &p_arrays, const Array &p_blend_shapes, uint32_t p_flags, int p_surface_id);
|
||||
void clear_cpu_surfaces();
|
||||
bool on_cpu() const { return _on_cpu && ((int)_cpu_surfaces.size() == surfaces.size()); }
|
||||
|
||||
protected:
|
||||
virtual bool _is_generated() const { return false; }
|
||||
|
||||
@ -237,6 +277,8 @@ public:
|
||||
|
||||
virtual void reload_from_file();
|
||||
|
||||
virtual void set_storage_mode(StorageMode p_storage_mode);
|
||||
|
||||
ArrayMesh();
|
||||
|
||||
~ArrayMesh();
|
||||
|
@ -789,6 +789,62 @@ void SurfaceTool::create_from_blend_shape(const Ref<Mesh> &p_existing, int p_sur
|
||||
_create_list_from_arrays(arr[shape_idx], &vertex_array, &index_array, format);
|
||||
}
|
||||
|
||||
// returns number of indices found within the subset
|
||||
int SurfaceTool::create_from_subset(const SurfaceTool &p_source, const LocalVector<uint32_t> &p_ids, uint32_t p_subset_id) {
|
||||
clear();
|
||||
|
||||
bool was_indexed = p_source.index_array.size() != 0;
|
||||
|
||||
// expecting deindexed input for now as easier to deal with
|
||||
ERR_FAIL_COND_V(was_indexed, 0);
|
||||
|
||||
// only deals with triangles
|
||||
ERR_FAIL_COND_V(p_source.primitive != Mesh::PRIMITIVE_TRIANGLES, 0);
|
||||
primitive = p_source.primitive;
|
||||
|
||||
uint32_t num_source_tris = p_source.vertex_array.size() / 3;
|
||||
DEV_ASSERT((p_source.vertex_array.size() % 3) == 0);
|
||||
|
||||
ERR_FAIL_COND_V(num_source_tris != p_ids.size(), 0);
|
||||
|
||||
const Vertex *v[3];
|
||||
const Vertex *input = p_source.vertex_array.ptr();
|
||||
|
||||
HashMap<Vertex, int, VertexHasher> indices;
|
||||
|
||||
for (uint32_t t = 0; t < num_source_tris; t++) {
|
||||
v[0] = input++;
|
||||
v[1] = input++;
|
||||
v[2] = input++;
|
||||
|
||||
if (p_ids[t] == p_subset_id) {
|
||||
// we can use this triangle
|
||||
for (int i = 0; i < 3; i++) {
|
||||
const Vertex &vert = *v[i];
|
||||
|
||||
int *idxptr = indices.getptr(vert);
|
||||
|
||||
int idx;
|
||||
if (!idxptr) {
|
||||
idx = indices.size();
|
||||
vertex_array.push_back(vert);
|
||||
indices[vert] = idx;
|
||||
} else {
|
||||
idx = *idxptr;
|
||||
}
|
||||
|
||||
index_array.push_back(idx);
|
||||
} // for i
|
||||
} // bound intersects
|
||||
}
|
||||
|
||||
// steal the format from the source surface tool
|
||||
format = p_source.format;
|
||||
format |= Mesh::ARRAY_FORMAT_INDEX;
|
||||
|
||||
return get_num_draw_vertices();
|
||||
}
|
||||
|
||||
void SurfaceTool::append_from(const Ref<Mesh> &p_existing, int p_surface, const Transform &p_xform) {
|
||||
ERR_FAIL_COND_MSG(p_existing.is_null(), "First argument in SurfaceTool::append_from() must be a valid object of type Mesh");
|
||||
if (vertex_array.size() == 0) {
|
||||
|
@ -37,6 +37,7 @@
|
||||
|
||||
class SurfaceTool : public Reference {
|
||||
GDCLASS(SurfaceTool, Reference);
|
||||
friend class MergingTool;
|
||||
|
||||
public:
|
||||
struct Vertex {
|
||||
@ -148,6 +149,7 @@ public:
|
||||
void create_from_blend_shape(const Ref<Mesh> &p_existing, int p_surface, const String &p_blend_shape_name);
|
||||
void append_from(const Ref<Mesh> &p_existing, int p_surface, const Transform &p_xform);
|
||||
Ref<ArrayMesh> commit(const Ref<ArrayMesh> &p_existing = Ref<ArrayMesh>(), uint32_t p_flags = Mesh::ARRAY_COMPRESS_DEFAULT);
|
||||
int create_from_subset(const SurfaceTool &p_source, const LocalVector<uint32_t> &p_ids, uint32_t p_subset_id);
|
||||
|
||||
SurfaceTool();
|
||||
};
|
||||
|
@ -1030,47 +1030,28 @@ void VisualServer::mesh_surface_make_offsets_from_format(uint32_t p_format, int
|
||||
}
|
||||
}
|
||||
|
||||
void VisualServer::mesh_add_surface_from_arrays(RID p_mesh, PrimitiveType p_primitive, const Array &p_arrays, const Array &p_blend_shapes, uint32_t p_compress_format) {
|
||||
ERR_FAIL_INDEX(p_primitive, VS::PRIMITIVE_MAX);
|
||||
ERR_FAIL_COND(p_arrays.size() != VS::ARRAY_MAX);
|
||||
|
||||
bool use_split_stream = GLOBAL_GET("rendering/misc/mesh_storage/split_stream") && !(p_compress_format & VS::ARRAY_FLAG_USE_DYNAMIC_UPDATE);
|
||||
|
||||
uint32_t format = 0;
|
||||
|
||||
// validation
|
||||
int index_array_len = 0;
|
||||
int array_len = 0;
|
||||
// This function is separated from the main mesh_add_surface_from_arrays() to allow finding the format WITHOUT creating data.
|
||||
// This is necessary for CPU meshes, where we may want to know the final format without creating final data.
|
||||
bool VisualServer::_mesh_find_format(VS::PrimitiveType p_primitive, const Array &p_arrays, const Array &p_blend_shapes, uint32_t p_compress_format, bool p_use_split_stream, uint32_t r_offsets[], int &r_attributes_base_offset, int &r_attributes_stride, int &r_positions_stride, uint32_t &r_format, int &r_index_array_len, int &r_array_len) {
|
||||
ERR_FAIL_INDEX_V(p_primitive, VS::PRIMITIVE_MAX, false);
|
||||
ERR_FAIL_COND_V(p_arrays.size() != VS::ARRAY_MAX, false);
|
||||
|
||||
for (int i = 0; i < p_arrays.size(); i++) {
|
||||
if (p_arrays[i].get_type() == Variant::NIL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
format |= (1 << i);
|
||||
r_format |= (1 << i);
|
||||
|
||||
if (i == VS::ARRAY_VERTEX) {
|
||||
Variant var = p_arrays[i];
|
||||
switch (var.get_type()) {
|
||||
case Variant::POOL_VECTOR2_ARRAY: {
|
||||
PoolVector<Vector2> v2 = var;
|
||||
} break;
|
||||
case Variant::POOL_VECTOR3_ARRAY: {
|
||||
PoolVector<Vector3> v3 = var;
|
||||
} break;
|
||||
default: {
|
||||
Array v = var;
|
||||
} break;
|
||||
}
|
||||
|
||||
array_len = PoolVector3Array(p_arrays[i]).size();
|
||||
ERR_FAIL_COND(array_len == 0);
|
||||
r_array_len = PoolVector3Array(p_arrays[i]).size();
|
||||
ERR_FAIL_COND_V(r_array_len == 0, false);
|
||||
} else if (i == VS::ARRAY_INDEX) {
|
||||
index_array_len = PoolIntArray(p_arrays[i]).size();
|
||||
r_index_array_len = PoolIntArray(p_arrays[i]).size();
|
||||
}
|
||||
}
|
||||
|
||||
ERR_FAIL_COND((format & VS::ARRAY_FORMAT_VERTEX) == 0); // mandatory
|
||||
ERR_FAIL_COND_V((r_format & VS::ARRAY_FORMAT_VERTEX) == 0, false); // mandatory
|
||||
|
||||
if (p_blend_shapes.size()) {
|
||||
//validate format for morphs
|
||||
@ -1083,21 +1064,14 @@ void VisualServer::mesh_add_surface_from_arrays(RID p_mesh, PrimitiveType p_prim
|
||||
}
|
||||
}
|
||||
|
||||
ERR_FAIL_COND((bsformat) != (format & (VS::ARRAY_FORMAT_INDEX - 1)));
|
||||
ERR_FAIL_COND_V((bsformat) != (r_format & (VS::ARRAY_FORMAT_INDEX - 1)), false);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t offsets[VS::ARRAY_MAX];
|
||||
uint32_t strides[VS::ARRAY_MAX];
|
||||
|
||||
int attributes_base_offset = 0;
|
||||
int attributes_stride = 0;
|
||||
int positions_stride = 0;
|
||||
|
||||
for (int i = 0; i < VS::ARRAY_MAX; i++) {
|
||||
offsets[i] = 0; //reset
|
||||
r_offsets[i] = 0; //reset
|
||||
|
||||
if (!(format & (1 << i))) { // no array
|
||||
if (!(r_format & (1 << i))) { // no array
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -1108,15 +1082,15 @@ void VisualServer::mesh_add_surface_from_arrays(RID p_mesh, PrimitiveType p_prim
|
||||
Variant arr = p_arrays[0];
|
||||
if (arr.get_type() == Variant::POOL_VECTOR2_ARRAY) {
|
||||
elem_size = 2;
|
||||
p_compress_format |= ARRAY_FLAG_USE_2D_VERTICES;
|
||||
p_compress_format |= VS::ARRAY_FLAG_USE_2D_VERTICES;
|
||||
} else if (arr.get_type() == Variant::POOL_VECTOR3_ARRAY) {
|
||||
p_compress_format &= ~ARRAY_FLAG_USE_2D_VERTICES;
|
||||
p_compress_format &= ~VS::ARRAY_FLAG_USE_2D_VERTICES;
|
||||
elem_size = 3;
|
||||
} else {
|
||||
elem_size = (p_compress_format & ARRAY_FLAG_USE_2D_VERTICES) ? 2 : 3;
|
||||
elem_size = (p_compress_format & VS::ARRAY_FLAG_USE_2D_VERTICES) ? 2 : 3;
|
||||
}
|
||||
|
||||
if (p_compress_format & ARRAY_COMPRESS_VERTEX) {
|
||||
if (p_compress_format & VS::ARRAY_COMPRESS_VERTEX) {
|
||||
elem_size *= sizeof(int16_t);
|
||||
} else {
|
||||
elem_size *= sizeof(float);
|
||||
@ -1127,94 +1101,94 @@ void VisualServer::mesh_add_surface_from_arrays(RID p_mesh, PrimitiveType p_prim
|
||||
elem_size = 8;
|
||||
}
|
||||
|
||||
offsets[i] = 0;
|
||||
positions_stride = elem_size;
|
||||
if (use_split_stream) {
|
||||
attributes_base_offset = elem_size * array_len;
|
||||
r_offsets[i] = 0;
|
||||
r_positions_stride = elem_size;
|
||||
if (p_use_split_stream) {
|
||||
r_attributes_base_offset = elem_size * r_array_len;
|
||||
} else {
|
||||
attributes_base_offset = elem_size;
|
||||
r_attributes_base_offset = elem_size;
|
||||
}
|
||||
|
||||
} break;
|
||||
case VS::ARRAY_NORMAL: {
|
||||
if (p_compress_format & ARRAY_FLAG_USE_OCTAHEDRAL_COMPRESSION) {
|
||||
if (p_compress_format & VS::ARRAY_FLAG_USE_OCTAHEDRAL_COMPRESSION) {
|
||||
// normal will always be oct32 (4 byte) encoded
|
||||
// UNLESS tangent exists and is also compressed
|
||||
// then it will be oct16 encoded along with tangent
|
||||
if ((p_compress_format & ARRAY_COMPRESS_NORMAL) && (format & ARRAY_FORMAT_TANGENT) && (p_compress_format & ARRAY_COMPRESS_TANGENT)) {
|
||||
if ((p_compress_format & VS::ARRAY_COMPRESS_NORMAL) && (r_format & VS::ARRAY_FORMAT_TANGENT) && (p_compress_format & VS::ARRAY_COMPRESS_TANGENT)) {
|
||||
elem_size = sizeof(uint8_t) * 2;
|
||||
} else {
|
||||
elem_size = sizeof(uint16_t) * 2;
|
||||
}
|
||||
} else {
|
||||
if (p_compress_format & ARRAY_COMPRESS_NORMAL) {
|
||||
if (p_compress_format & VS::ARRAY_COMPRESS_NORMAL) {
|
||||
elem_size = sizeof(uint32_t);
|
||||
} else {
|
||||
elem_size = sizeof(float) * 3;
|
||||
}
|
||||
}
|
||||
offsets[i] = attributes_base_offset + attributes_stride;
|
||||
attributes_stride += elem_size;
|
||||
r_offsets[i] = r_attributes_base_offset + r_attributes_stride;
|
||||
r_attributes_stride += elem_size;
|
||||
|
||||
} break;
|
||||
|
||||
case VS::ARRAY_TANGENT: {
|
||||
if (p_compress_format & ARRAY_FLAG_USE_OCTAHEDRAL_COMPRESSION) {
|
||||
if (p_compress_format & ARRAY_COMPRESS_TANGENT && (format & ARRAY_FORMAT_NORMAL) && (p_compress_format & ARRAY_COMPRESS_NORMAL)) {
|
||||
if (p_compress_format & VS::ARRAY_FLAG_USE_OCTAHEDRAL_COMPRESSION) {
|
||||
if (p_compress_format & VS::ARRAY_COMPRESS_TANGENT && (r_format & VS::ARRAY_FORMAT_NORMAL) && (p_compress_format & VS::ARRAY_COMPRESS_NORMAL)) {
|
||||
elem_size = sizeof(uint8_t) * 2;
|
||||
} else {
|
||||
elem_size = sizeof(uint16_t) * 2;
|
||||
}
|
||||
} else {
|
||||
if (p_compress_format & ARRAY_COMPRESS_TANGENT) {
|
||||
if (p_compress_format & VS::ARRAY_COMPRESS_TANGENT) {
|
||||
elem_size = sizeof(uint32_t);
|
||||
} else {
|
||||
elem_size = sizeof(float) * 4;
|
||||
}
|
||||
}
|
||||
offsets[i] = attributes_base_offset + attributes_stride;
|
||||
attributes_stride += elem_size;
|
||||
r_offsets[i] = r_attributes_base_offset + r_attributes_stride;
|
||||
r_attributes_stride += elem_size;
|
||||
|
||||
} break;
|
||||
case VS::ARRAY_COLOR: {
|
||||
if (p_compress_format & ARRAY_COMPRESS_COLOR) {
|
||||
if (p_compress_format & VS::ARRAY_COMPRESS_COLOR) {
|
||||
elem_size = sizeof(uint32_t);
|
||||
} else {
|
||||
elem_size = sizeof(float) * 4;
|
||||
}
|
||||
offsets[i] = attributes_base_offset + attributes_stride;
|
||||
attributes_stride += elem_size;
|
||||
r_offsets[i] = r_attributes_base_offset + r_attributes_stride;
|
||||
r_attributes_stride += elem_size;
|
||||
|
||||
} break;
|
||||
case VS::ARRAY_TEX_UV: {
|
||||
if (p_compress_format & ARRAY_COMPRESS_TEX_UV) {
|
||||
if (p_compress_format & VS::ARRAY_COMPRESS_TEX_UV) {
|
||||
elem_size = sizeof(uint32_t);
|
||||
} else {
|
||||
elem_size = sizeof(float) * 2;
|
||||
}
|
||||
offsets[i] = attributes_base_offset + attributes_stride;
|
||||
attributes_stride += elem_size;
|
||||
r_offsets[i] = r_attributes_base_offset + r_attributes_stride;
|
||||
r_attributes_stride += elem_size;
|
||||
|
||||
} break;
|
||||
|
||||
case VS::ARRAY_TEX_UV2: {
|
||||
if (p_compress_format & ARRAY_COMPRESS_TEX_UV2) {
|
||||
if (p_compress_format & VS::ARRAY_COMPRESS_TEX_UV2) {
|
||||
elem_size = sizeof(uint32_t);
|
||||
} else {
|
||||
elem_size = sizeof(float) * 2;
|
||||
}
|
||||
offsets[i] = attributes_base_offset + attributes_stride;
|
||||
attributes_stride += elem_size;
|
||||
r_offsets[i] = r_attributes_base_offset + r_attributes_stride;
|
||||
r_attributes_stride += elem_size;
|
||||
|
||||
} break;
|
||||
case VS::ARRAY_WEIGHTS: {
|
||||
if (p_compress_format & ARRAY_COMPRESS_WEIGHTS) {
|
||||
if (p_compress_format & VS::ARRAY_COMPRESS_WEIGHTS) {
|
||||
elem_size = sizeof(uint16_t) * 4;
|
||||
} else {
|
||||
elem_size = sizeof(float) * 4;
|
||||
}
|
||||
offsets[i] = attributes_base_offset + attributes_stride;
|
||||
attributes_stride += elem_size;
|
||||
r_offsets[i] = r_attributes_base_offset + r_attributes_stride;
|
||||
r_attributes_stride += elem_size;
|
||||
|
||||
} break;
|
||||
case VS::ARRAY_BONES: {
|
||||
@ -1230,37 +1204,83 @@ void VisualServer::mesh_add_surface_from_arrays(RID p_mesh, PrimitiveType p_prim
|
||||
}
|
||||
|
||||
if (max_bone > 255) {
|
||||
p_compress_format |= ARRAY_FLAG_USE_16_BIT_BONES;
|
||||
p_compress_format |= VS::ARRAY_FLAG_USE_16_BIT_BONES;
|
||||
elem_size = sizeof(uint16_t) * 4;
|
||||
} else {
|
||||
p_compress_format &= ~ARRAY_FLAG_USE_16_BIT_BONES;
|
||||
p_compress_format &= ~VS::ARRAY_FLAG_USE_16_BIT_BONES;
|
||||
elem_size = sizeof(uint32_t);
|
||||
}
|
||||
offsets[i] = attributes_base_offset + attributes_stride;
|
||||
attributes_stride += elem_size;
|
||||
r_offsets[i] = r_attributes_base_offset + r_attributes_stride;
|
||||
r_attributes_stride += elem_size;
|
||||
|
||||
} break;
|
||||
case VS::ARRAY_INDEX: {
|
||||
if (index_array_len <= 0) {
|
||||
if (r_index_array_len <= 0) {
|
||||
ERR_PRINT("index_array_len==NO_INDEX_ARRAY");
|
||||
break;
|
||||
}
|
||||
/* determine whether using 16 or 32 bits indices */
|
||||
if (array_len >= (1 << 16)) {
|
||||
if (r_array_len >= (1 << 16)) {
|
||||
elem_size = 4;
|
||||
|
||||
} else {
|
||||
elem_size = 2;
|
||||
}
|
||||
offsets[i] = elem_size;
|
||||
r_offsets[i] = elem_size;
|
||||
continue;
|
||||
}
|
||||
default: {
|
||||
ERR_FAIL();
|
||||
ERR_FAIL_V(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t mask = (1 << VS::ARRAY_MAX) - 1;
|
||||
r_format |= (~mask) & p_compress_format; //make the full format
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t VisualServer::mesh_find_format_from_arrays(PrimitiveType p_primitive, const Array &p_arrays, const Array &p_blend_shapes, uint32_t p_compress_format) {
|
||||
bool use_split_stream = GLOBAL_GET("rendering/misc/mesh_storage/split_stream") && !(p_compress_format & VS::ARRAY_FLAG_USE_DYNAMIC_UPDATE);
|
||||
|
||||
uint32_t offsets[VS::ARRAY_MAX];
|
||||
|
||||
int attributes_base_offset = 0;
|
||||
int attributes_stride = 0;
|
||||
int positions_stride = 0;
|
||||
|
||||
uint32_t format = 0;
|
||||
|
||||
// validation
|
||||
int index_array_len = 0;
|
||||
int array_len = 0;
|
||||
|
||||
bool res = _mesh_find_format(p_primitive, p_arrays, p_blend_shapes, p_compress_format, use_split_stream, offsets, attributes_base_offset, attributes_stride, positions_stride, format, index_array_len, array_len);
|
||||
ERR_FAIL_COND_V(!res, 0);
|
||||
return format;
|
||||
}
|
||||
|
||||
void VisualServer::mesh_add_surface_from_arrays(RID p_mesh, PrimitiveType p_primitive, const Array &p_arrays, const Array &p_blend_shapes, uint32_t p_compress_format) {
|
||||
bool use_split_stream = GLOBAL_GET("rendering/misc/mesh_storage/split_stream") && !(p_compress_format & VS::ARRAY_FLAG_USE_DYNAMIC_UPDATE);
|
||||
|
||||
uint32_t offsets[VS::ARRAY_MAX];
|
||||
|
||||
int attributes_base_offset = 0;
|
||||
int attributes_stride = 0;
|
||||
int positions_stride = 0;
|
||||
|
||||
uint32_t format = 0;
|
||||
|
||||
// validation
|
||||
int index_array_len = 0;
|
||||
int array_len = 0;
|
||||
|
||||
bool res = _mesh_find_format(p_primitive, p_arrays, p_blend_shapes, p_compress_format, use_split_stream, offsets, attributes_base_offset, attributes_stride, positions_stride, format, index_array_len, array_len);
|
||||
ERR_FAIL_COND(!res);
|
||||
|
||||
uint32_t strides[VS::ARRAY_MAX];
|
||||
|
||||
if (use_split_stream) {
|
||||
strides[VS::ARRAY_VERTEX] = positions_stride;
|
||||
for (int i = 1; i < VS::ARRAY_MAX - 1; i++) {
|
||||
@ -1272,9 +1292,6 @@ void VisualServer::mesh_add_surface_from_arrays(RID p_mesh, PrimitiveType p_prim
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t mask = (1 << ARRAY_MAX) - 1;
|
||||
format |= (~mask) & p_compress_format; //make the full format
|
||||
|
||||
int array_size = (positions_stride + attributes_stride) * array_len;
|
||||
|
||||
PoolVector<uint8_t> vertex_array;
|
||||
|
@ -296,6 +296,9 @@ public:
|
||||
/// Returns stride
|
||||
virtual void mesh_surface_make_offsets_from_format(uint32_t p_format, int p_vertex_len, int p_index_len, uint32_t *r_offsets, uint32_t *r_strides) const;
|
||||
virtual void mesh_add_surface_from_arrays(RID p_mesh, PrimitiveType p_primitive, const Array &p_arrays, const Array &p_blend_shapes = Array(), uint32_t p_compress_format = ARRAY_COMPRESS_DEFAULT);
|
||||
virtual uint32_t mesh_find_format_from_arrays(PrimitiveType p_primitive, const Array &p_arrays, const Array &p_blend_shapes = Array(), uint32_t p_compress_format = ARRAY_COMPRESS_DEFAULT);
|
||||
bool _mesh_find_format(PrimitiveType p_primitive, const Array &p_arrays, const Array &p_blend_shapes, uint32_t p_compress_format, bool p_use_split_stream, uint32_t r_offsets[], int &r_attributes_base_offset, int &r_attributes_stride, int &r_positions_stride, uint32_t &r_format, int &r_index_array_len, int &r_array_len);
|
||||
|
||||
virtual void mesh_add_surface(RID p_mesh, uint32_t p_format, PrimitiveType p_primitive, const PoolVector<uint8_t> &p_array, int p_vertex_count, const PoolVector<uint8_t> &p_index_array, int p_index_count, const AABB &p_aabb, const Vector<PoolVector<uint8_t>> &p_blend_shapes = Vector<PoolVector<uint8_t>>(), const Vector<AABB> &p_bone_aabbs = Vector<AABB>()) = 0;
|
||||
|
||||
virtual void mesh_set_blend_shape_count(RID p_mesh, int p_amount) = 0;
|
||||
|
Loading…
Reference in New Issue
Block a user