diff --git a/README.md b/README.md new file mode 100644 index 0000000..7d57497 --- /dev/null +++ b/README.md @@ -0,0 +1,12 @@ +# Prop Tool + +This is an addon for https://github.com/Relintai/voxelman, to help with creating/editing props. + +Props are 3d obects in the world, they can contain lights (VoxelmanLight), meshes (MehDataResorce), and other Props, and scenes. + +VoxelmanLights are precalculated lights inside the cunks. +MeshDataResources exists, because you cannot access mesh data in gles2 from normal meshes. Voxelman merges all meshes that you add like this automatically. + +Looks like this: + +![prop_tool screenshot](screenshots/prop_tool.png) \ No newline at end of file diff --git a/player/PropToolDisplayPlayer.gd b/player/PropToolDisplayPlayer.gd new file mode 100644 index 0000000..6eeb644 --- /dev/null +++ b/player/PropToolDisplayPlayer.gd @@ -0,0 +1,3 @@ +tool +extends Entity + diff --git a/player/PropToolDisplayPlayer.tscn b/player/PropToolDisplayPlayer.tscn new file mode 100644 index 0000000..5e4270c --- /dev/null +++ b/player/PropToolDisplayPlayer.tscn @@ -0,0 +1,17 @@ +[gd_scene load_steps=3 format=2] + +[ext_resource path="res://data/models/armature_model_orig_v2.tscn" type="PackedScene" id=1] +[ext_resource path="res://addons/prop_tool/player/PropToolDisplayPlayer.gd" type="Script" id=2] + +[node name="DisplayPlayer" type="Entity" groups=[ +"players", +]] +input_ray_pickable = false +collision_layer = 3 +collision_mask = 3 +character_skeleton_path = NodePath("character") +script = ExtResource( 2 ) + +[node name="character" parent="." instance=ExtResource( 1 )] +transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, -0.00126277, 0.085327, -0.000325084 ) +use_threads = false diff --git a/plugin.cfg b/plugin.cfg new file mode 100644 index 0000000..e69f789 --- /dev/null +++ b/plugin.cfg @@ -0,0 +1,7 @@ +[plugin] + +name="Prop Tool" +description="" +author="Relintai" +version="" +script="plugin.gd" diff --git a/plugin.gd b/plugin.gd new file mode 100644 index 0000000..7030f07 --- /dev/null +++ b/plugin.gd @@ -0,0 +1,280 @@ +tool +extends EditorPlugin + +const main_scene_path : String = "res://addons/prop_tool/scenes/main.tscn" +const temp_scene_path : String = "res://addons/prop_tool/scenes/main_temp.tscn" +const temp_path : String = "res://addons/prop_tool/scenes/temp/" + +var buttons_added : bool = false +var light_button : ToolButton +var mesh_button : ToolButton +var prop_button : ToolButton +var scene_button : ToolButton +var entity_button : ToolButton + +var edited_prop : PropData + +func _enter_tree(): + var dir : Directory = Directory.new() + dir.copy(main_scene_path, temp_scene_path) + + light_button = ToolButton.new() + light_button.text = "Light" + light_button.connect("pressed", self, "add_light") + + mesh_button = ToolButton.new() + mesh_button.text = "Mesh" + mesh_button.connect("pressed", self, "add_mesh") + + prop_button = ToolButton.new() + prop_button.text = "Prop" + prop_button.connect("pressed", self, "add_prop") + + scene_button = ToolButton.new() + scene_button.text = "Scene" + scene_button.connect("pressed", self, "add_scene") + + entity_button = ToolButton.new() + entity_button.text = "Entity" + entity_button.connect("pressed", self, "add_entity") + + add_control_to_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_MENU, light_button) + add_control_to_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_MENU, mesh_button) + add_control_to_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_MENU, prop_button) + add_control_to_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_MENU, scene_button) + add_control_to_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_MENU, entity_button) + + light_button.hide() + mesh_button.hide() + prop_button.hide() + scene_button.hide() + entity_button.hide() + + connect("scene_changed", self, "scene_changed") + + +func _exit_tree(): + var dir : Directory = Directory.new() + dir.remove(temp_scene_path) + + if is_instance_valid(light_button): + light_button.queue_free() + + if is_instance_valid(mesh_button): + mesh_button.queue_free() + + if is_instance_valid(prop_button): + prop_button.queue_free() + + if is_instance_valid(scene_button): + scene_button.queue_free() + + if is_instance_valid(entity_button): + entity_button.queue_free() + + light_button = null + mesh_button = null + prop_button = null + scene_button = null + entity_button = null + + edited_prop = null + + disconnect("scene_changed", self, "scene_changed") + +func scene_changed(scene): + if scene.has_method("set_target_prop"): + scene.plugin = self + + if not buttons_added: + light_button.show() + mesh_button.show() + prop_button.show() + scene_button.show() + entity_button.show() +# add_control_to_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_MENU, light_button) +# add_control_to_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_MENU, mesh_button) +# add_control_to_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_MENU, prop_button) +# add_control_to_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_MENU, scene_button) +# add_control_to_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_MENU, entity_button) + + buttons_added = true + else: + if buttons_added: + light_button.hide() + mesh_button.hide() + prop_button.hide() + scene_button.hide() + entity_button.hide() +# +# remove_control_from_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_MENU, light_button) +# remove_control_from_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_MENU, mesh_button) +# remove_control_from_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_MENU, prop_button) +# remove_control_from_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_MENU, scene_button) +# remove_control_from_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_MENU, entity_button) + + buttons_added = false + +func apply_changes() -> void: + var scene : Node = get_editor_interface().get_edited_scene_root() + + if scene is PropTool: +# if scene.has_method("set_target_prop") and scene.has_method("save"): + scene.save() + +func edit(object : Object) -> void: + var pedited_prop = object as PropData + + var p : String = create_or_get_scene_path(pedited_prop) + + get_editor_interface().open_scene_from_path(p) + +func handles(object : Object) -> bool: + return object is PropData + +func add_light(): + var selection : Array = get_editor_interface().get_selection().get_selected_nodes() + var selected : Node + + if selection.size() != 1: + selected = get_editor_interface().get_edited_scene_root() + else: + selected = selection[0] + + var s : Node = selected + var n = PropToolLight.new() + + var u : UndoRedo = get_undo_redo() + u.create_action("Add Light") + u.add_do_method(s, "add_child", n) + u.add_do_property(n, "owner", get_editor_interface().get_edited_scene_root()) + u.add_undo_method(s, "remove_child", n) + u.commit_action() + + get_editor_interface().get_selection().clear() + get_editor_interface().get_selection().add_node(n) + + +func add_mesh(): + var selected : Array = get_editor_interface().get_selection().get_selected_nodes() + + if selected.size() != 1: + return + + var s : Node = selected[0] + var n = PropToolMesh.new() + + var u : UndoRedo = get_undo_redo() + u.create_action("Add Mesh") + u.add_do_method(s, "add_child", n) + u.add_do_property(n, "owner", get_editor_interface().get_edited_scene_root()) + u.add_undo_method(s, "remove_child", n) + u.commit_action() + + get_editor_interface().get_selection().clear() + get_editor_interface().get_selection().add_node(n) + + +func add_prop(): + var selected : Array = get_editor_interface().get_selection().get_selected_nodes() + + if selected.size() != 1: + return + + var s : Node = selected[0] + var n = PropTool.new() + + var u : UndoRedo = get_undo_redo() + u.create_action("Add Prop") + u.add_do_method(s, "add_child", n) + u.add_do_property(n, "owner", get_editor_interface().get_edited_scene_root()) + u.add_undo_method(s, "remove_child", n) + u.commit_action() + + get_editor_interface().get_selection().clear() + get_editor_interface().get_selection().add_node(n) + +func add_scene(): + var selected : Array = get_editor_interface().get_selection().get_selected_nodes() + + if selected.size() != 1: + return + + var s : Node = selected[0] + var n = PropToolScene.new() + + var u : UndoRedo = get_undo_redo() + u.create_action("Add Scene") + u.add_do_method(s, "add_child", n) + u.add_do_property(n, "owner", get_editor_interface().get_edited_scene_root()) + u.add_undo_method(s, "remove_child", n) + u.commit_action() + + get_editor_interface().get_selection().clear() + get_editor_interface().get_selection().add_node(n) + +func add_entity(): + var selected : Array = get_editor_interface().get_selection().get_selected_nodes() + + if selected.size() != 1: + return + + var s : Node = selected[0] + var n = PropToolEntity.new() + + var u : UndoRedo = get_undo_redo() + u.create_action("Add Entity") + u.add_do_method(s, "add_child", n) + u.add_do_property(n, "owner", get_editor_interface().get_edited_scene_root()) + u.add_undo_method(s, "remove_child", n) + u.commit_action() + + get_editor_interface().get_selection().clear() + get_editor_interface().get_selection().add_node(n) + +func create_or_get_scene_path(data: PropData) -> String: + var path : String = temp_path + data.resource_path.get_file().get_basename() + ".tscn" + + var dir : Directory = Directory.new() + + if not dir.file_exists(path): + create_scene(data) + + return path + +func create_or_get_scene(data: PropData) -> PropTool: + if data == null: + print("PropTool plugin.gd: Data is null!") + return null + + var path : String = temp_path + data.resource_path.get_file().get_basename() + ".tscn" + + var dir : Directory = Directory.new() + + var ps : PackedScene + + if not dir.file_exists(path): + ps = create_scene(data) + else: + ps = ResourceLoader.load(path, "PackedScene") + + if ps == null: + return null + + var pt : PropTool = ps.instance() as PropTool + pt.plugin = self + return pt + +func create_scene(data: PropData) -> PackedScene: + var pt : PropTool = PropTool.new() + + pt.plugin = self + pt.set_target_prop(data) + + var ps : PackedScene = PackedScene.new() + ps.pack(pt) + + var err = ResourceSaver.save(temp_path + data.resource_path.get_file().get_basename() + ".tscn", ps) + + pt.queue_free() + return ps diff --git a/scenes/.gitignore b/scenes/.gitignore new file mode 100644 index 0000000..2283bd2 --- /dev/null +++ b/scenes/.gitignore @@ -0,0 +1 @@ +main_temp.tscn \ No newline at end of file diff --git a/scenes/main.tscn b/scenes/main.tscn new file mode 100644 index 0000000..5cb6f9e --- /dev/null +++ b/scenes/main.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=2] + +[ext_resource path="res://addons/prop_tool/tools/PropTool.gd" type="Script" id=1] + +[node name="PropTool" type="Spatial"] +script = ExtResource( 1 ) diff --git a/scenes/temp/.gitignore b/scenes/temp/.gitignore new file mode 100644 index 0000000..54010b9 --- /dev/null +++ b/scenes/temp/.gitignore @@ -0,0 +1 @@ +*.tscn diff --git a/tools/PropTool.gd b/tools/PropTool.gd new file mode 100644 index 0000000..508ba98 --- /dev/null +++ b/tools/PropTool.gd @@ -0,0 +1,152 @@ +tool +extends Spatial +class_name PropTool + +export(bool) var refresh : bool setget refresh_set +export(PropData) var target_prop : PropData setget target_prop_set +export(bool) var snap_to_mesh : bool = false +export(Vector3) var snap_axis : Vector3 = Vector3(0, -1, 0) +var plugin : EditorPlugin + +func save() -> void: + if target_prop == null: + return + + print("save " + target_prop.resource_path) + + while target_prop.get_prop_count() > 0: + target_prop.remove_prop(0) + + for child in get_children(): + save_node(child, transform) + + target_prop.snap_to_mesh = snap_to_mesh + target_prop.snap_axis = snap_axis + + ResourceSaver.save(target_prop.resource_path, target_prop) + +func save_node(node : Node, parent_transform: Transform) -> void: + if node is Spatial and node.has_method("get_data"): + var prop : PropDataEntry = node.get_data() + + if prop: + prop.transform = parent_transform * (node as Spatial).transform + + target_prop.add_prop(prop) + + if node.has_method("evaluate_children") and not node.evaluate_children(): + return + + for child in node.get_children(): + save_node(child, parent_transform * node.transform) + else: + if node.has_method("set_target_prop"): + if node.target_prop: + var prop : PropDataProp = PropDataProp.new() + + prop.prop = node.target_prop + + prop.transform = parent_transform * (node as Spatial).transform + + target_prop.add_prop(prop) + else: + for child in node.get_children(): + save_node(child, parent_transform) + +func rebuild_hierarchy() -> void: + for ch in get_children(): + ch.queue_free() + + if target_prop == null: + return + + snap_to_mesh = target_prop.snap_to_mesh + snap_axis = target_prop.snap_axis + + for i in range(target_prop.get_prop_count()): + print(i) + var prop : PropDataEntry = target_prop.get_prop(i) + + if prop is PropDataLight: + var l : PropToolLight = PropToolLight.new() + + add_child(l) + l.owner = self + l.transform = prop.transform + + l.set_data(prop as PropDataLight) + elif prop is PropDataMesh: + var m : PropToolMesh = PropToolMesh.new() + + add_child(m) + m.owner = self + m.transform = prop.transform + + m.set_data(prop as PropDataMesh) + elif prop is PropDataScene: + var s : PropToolScene = PropToolScene.new() + + add_child(s) + s.owner = self + s.transform = prop.transform + + s.set_data(prop as PropDataScene) + elif prop is PropDataProp: + var s : Node = plugin.create_or_get_scene(prop.prop) + + add_child(s) + s.owner = self + s.transform = prop.transform +# s.set_target_prop(prop.prop) + elif prop is PropDataEntity: + var s : PropToolEntity = PropToolEntity.new() + + add_child(s) + s.owner = self + s.transform = prop.transform + + s.set_data(prop as PropDataEntity) + +func refresh_set(value): + if value: + rebuild_hierarchy() + +func set_target_prop(prop: PropData) -> void: +# if prop == null: +# return + + target_prop = prop + + rebuild_hierarchy() + +func get_plugin(): + return plugin + +func target_prop_set(prop: PropData) -> void: + target_prop = prop + + if prop == null: + return + + var last_prop_tool : PropTool = self + var root : Node = self + while root.get_parent() != null: + root = root.get_parent() + + if root and root.has_method("target_prop_set"): + last_prop_tool = root as PropTool + + if last_prop_tool == self: + return + + last_prop_tool.load_scene_for(self, prop) + +func load_scene_for(t: PropTool, prop: PropData): + if not plugin: + return + + t.queue_free() + var s : Node = plugin.create_or_get_scene(prop) + + add_child(s) + s.owner = self diff --git a/tools/PropToolEntity.gd b/tools/PropToolEntity.gd new file mode 100644 index 0000000..834db06 --- /dev/null +++ b/tools/PropToolEntity.gd @@ -0,0 +1,71 @@ +tool +extends Spatial +class_name PropToolEntity + +export(EntityData) var entity_data : EntityData setget set_entity_data +export(int) var entity_data_id : int +export(int) var level : int = 1 + +var _prop_entity : PropDataEntity +var _entity : Entity + +func get_data() -> PropDataEntity: + if not visible or entity_data == null: + return null + + if _prop_entity == null: + _prop_entity = PropDataEntity.new() + + _prop_entity.entity_data_id = entity_data.id + _prop_entity.level = level + + return _prop_entity + +func set_data(scene: PropDataEntity) -> void: + _prop_entity = scene + + entity_data_id = _prop_entity.entity_data_id + level = _prop_entity.level + + var dir = Directory.new() + if dir.open("res://data/entities/") == OK: + dir.list_dir_begin() + var file_name = dir.get_next() + + while (file_name != ""): + if not dir.current_is_dir(): + var ed : EntityData = ResourceLoader.load("res://data/entities/" + file_name, "EntityData") + + if ed != null and ed.id == entity_data_id: + set_entity_data(ed) + return + + file_name = dir.get_next() + + print("PropToolEntity: Entity not found!") + +func set_entity_data(ed: EntityData) -> void: + entity_data = ed + + if entity_data == null: + return + + if _entity == null: + var scene : PackedScene = load("res://addons/prop_tool/player/PropToolDisplayPlayer.tscn") + + _entity = scene.instance() as Entity + + add_child(_entity) +# _entity.owner = owner + +# _entity.get_node(_entity.character_skeleton_path).owner = owner + _entity.get_node(_entity.character_skeleton_path).refresh_in_editor = true + # _entity.get_character_skeleton().refresh_in_editor = true + + _entity.sentity_data = entity_data + + name = entity_data.text_name + + +func evaluate_children() -> bool: + return false diff --git a/tools/PropToolLight.gd b/tools/PropToolLight.gd new file mode 100644 index 0000000..55c8694 --- /dev/null +++ b/tools/PropToolLight.gd @@ -0,0 +1,36 @@ +tool +extends OmniLight +class_name PropToolLight + +var _prop_light : PropDataLight + +#func _ready(): +# set_notify_transform(true) + +func get_data() -> PropDataLight: + if not visible: + return null + + if _prop_light == null: + _prop_light = PropDataLight.new() + + _prop_light.light_color = light_color + _prop_light.light_size = omni_range + + return _prop_light + +func set_data(light : PropDataLight) -> void: + _prop_light = light + + light_color = _prop_light.light_color + omni_range = _prop_light.light_size + light_energy = _prop_light.light_size + +#func save(): +# if get_node("..").has_method("save"): +# get_node("..").save() +# +#func _notification(what): +# if what == NOTIFICATION_TRANSFORM_CHANGED: +# save() + diff --git a/tools/PropToolMesh.gd b/tools/PropToolMesh.gd new file mode 100644 index 0000000..45baee8 --- /dev/null +++ b/tools/PropToolMesh.gd @@ -0,0 +1,84 @@ +tool +extends MeshInstance +class_name PropToolMesh + +export(MeshDataResource) var mesh_data : MeshDataResource setget set_prop_mesh +export(Texture) var texture : Texture setget set_texture +export(bool) var snap_to_mesh : bool = false +export(Vector3) var snap_axis : Vector3 = Vector3(0, -1, 0) + +export(bool) var generate : bool setget set_generate + +var _prop_mesh : PropDataMesh +var _material : SpatialMaterial + +func get_data() -> PropDataMesh: + if not visible or mesh_data == null: + return null + + if _prop_mesh == null: + _prop_mesh = PropDataMesh.new() + + _prop_mesh.mesh = mesh_data + _prop_mesh.texture = texture + _prop_mesh.snap_to_mesh = snap_to_mesh + _prop_mesh.snap_axis = snap_axis + + return _prop_mesh + +func set_data(data : PropDataMesh) -> void: + _prop_mesh = data + + set_texture(_prop_mesh.texture) + set_prop_mesh(_prop_mesh.mesh) + + snap_to_mesh = _prop_mesh.snap_to_mesh + snap_axis = _prop_mesh.snap_axis + +func set_prop_mesh(md : MeshDataResource) -> void: + mesh_data = md + + set_generate(true) + +func set_texture(tex : Texture) -> void: + texture = tex + +func set_generate(val : bool) -> void: + if val: + if !mesh_data: + mesh = null + return + + var m : ArrayMesh = ArrayMesh.new() + + var arr = [] + arr.resize(ArrayMesh.ARRAY_MAX) + + var v : PoolVector3Array = PoolVector3Array() + v.append_array(mesh_data.array[Mesh.ARRAY_VERTEX]) + arr[Mesh.ARRAY_VERTEX] = v + + var norm : PoolVector3Array = PoolVector3Array() + norm.append_array(mesh_data.array[Mesh.ARRAY_NORMAL]) + arr[Mesh.ARRAY_NORMAL] = norm + + var uv : PoolVector2Array = PoolVector2Array() + uv.append_array(mesh_data.array[Mesh.ARRAY_TEX_UV]) + arr[Mesh.ARRAY_TEX_UV] = uv + + var ind : PoolIntArray = PoolIntArray() + ind.append_array(mesh_data.array[Mesh.ARRAY_INDEX]) + arr[Mesh.ARRAY_INDEX] = ind + + m.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arr) + + mesh = m + + if texture != null: + if _material == null: + _material = SpatialMaterial.new() + + _material.albedo_texture = texture + + material_override = _material + diff --git a/tools/PropToolProp.gd b/tools/PropToolProp.gd new file mode 100644 index 0000000..c2b3a0a --- /dev/null +++ b/tools/PropToolProp.gd @@ -0,0 +1,32 @@ +tool +extends Spatial +class_name PropToolProp + +export(PropData) var prop_data : PropData +export(bool) var snap_to_mesh : bool = false +export(Vector3) var snap_axis : Vector3 = Vector3(0, -1, 0) + +var _prop_prop : PropDataProp + +func get_data() -> PropDataProp: + if not visible or prop_data == null: + return null + + if _prop_prop == null: + _prop_prop = PropDataProp.new() + + _prop_prop.prop = prop_data + _prop_prop.snap_to_mesh = snap_to_mesh + _prop_prop.snap_axis = snap_axis + + return _prop_prop + +func set_data(prop: PropDataProp) -> void: + _prop_prop = prop + + prop_data = _prop_prop.prop + snap_to_mesh = prop_data.snap_to_mesh + snap_axis = prop_data.snap_axis + +func refresh() -> void: + return diff --git a/tools/PropToolScene.gd b/tools/PropToolScene.gd new file mode 100644 index 0000000..4a1191c --- /dev/null +++ b/tools/PropToolScene.gd @@ -0,0 +1,30 @@ +tool +extends Spatial +class_name PropToolScene + +export(PackedScene) var scene_data : PackedScene +export(bool) var snap_to_mesh : bool = false +export(Vector3) var snap_axis : Vector3 = Vector3(0, -1, 0) + +var _prop_scene : PropDataScene + +func get_data() -> PropDataScene: + if not visible or scene_data == null: + return null + + if _prop_scene == null: + _prop_scene = PropDataScene.new() + + _prop_scene.scene = scene_data + _prop_scene.snap_to_mesh = snap_to_mesh + _prop_scene.snap_axis = snap_axis + + return _prop_scene + +func set_data(scene: PropDataScene) -> void: + _prop_scene = scene + + scene_data = _prop_scene.scene + snap_to_mesh = _prop_scene.snap_to_mesh + snap_axis = _prop_scene.snap_axis +