diff --git a/PixelComposer.resource_order b/PixelComposer.resource_order index 191f53864..08d1d6f53 100644 --- a/PixelComposer.resource_order +++ b/PixelComposer.resource_order @@ -637,6 +637,7 @@ {"name":"node_3d_material","order":10,"path":"scripts/node_3d_material/node_3d_material.yy",}, {"name":"node_3d_mesh_cone","order":6,"path":"scripts/node_3d_mesh_cone/node_3d_mesh_cone.yy",}, {"name":"node_3d_mesh_cylinder","order":3,"path":"scripts/node_3d_mesh_cylinder/node_3d_mesh_cylinder.yy",}, + {"name":"node_3d_mesh_export","order":14,"path":"scripts/node_3d_mesh_export/node_3d_mesh_export.yy",}, {"name":"node_3d_mesh_extrude","order":8,"path":"scripts/node_3d_mesh_extrude/node_3d_mesh_extrude.yy",}, {"name":"node_3d_mesh_obj","order":2,"path":"scripts/node_3d_mesh_obj/node_3d_mesh_obj.yy",}, {"name":"node_3d_mesh_plane","order":7,"path":"scripts/node_3d_mesh_plane/node_3d_mesh_plane.yy",}, @@ -1630,6 +1631,7 @@ {"name":"s_node_3d_light_point","order":18,"path":"sprites/s_node_3d_light_point/s_node_3d_light_point.yy",}, {"name":"s_node_3d_meterial","order":12,"path":"sprites/s_node_3d_meterial/s_node_3d_meterial.yy",}, {"name":"s_node_3d_obj_combine","order":5,"path":"sprites/s_node_3d_obj_combine/s_node_3d_obj_combine.yy",}, + {"name":"s_node_3d_obj_export","order":30,"path":"sprites/s_node_3d_obj_export/s_node_3d_obj_export.yy",}, {"name":"s_node_3d_obj","order":3,"path":"sprites/s_node_3d_obj/s_node_3d_obj.yy",}, {"name":"s_node_3d_plane","order":6,"path":"sprites/s_node_3d_plane/s_node_3d_plane.yy",}, {"name":"s_node_3d_point_affector","order":21,"path":"sprites/s_node_3d_point_affector/s_node_3d_point_affector.yy",}, diff --git a/PixelComposer.yyp b/PixelComposer.yyp index cea88af0d..374bc6afc 100644 --- a/PixelComposer.yyp +++ b/PixelComposer.yyp @@ -1017,6 +1017,7 @@ {"id":{"name":"node_3d_mesh_cone","path":"scripts/node_3d_mesh_cone/node_3d_mesh_cone.yy",},}, {"id":{"name":"node_3d_mesh_cube","path":"scripts/node_3d_mesh_cube/node_3d_mesh_cube.yy",},}, {"id":{"name":"node_3d_mesh_cylinder","path":"scripts/node_3d_mesh_cylinder/node_3d_mesh_cylinder.yy",},}, + {"id":{"name":"node_3d_mesh_export","path":"scripts/node_3d_mesh_export/node_3d_mesh_export.yy",},}, {"id":{"name":"node_3d_mesh_extrude","path":"scripts/node_3d_mesh_extrude/node_3d_mesh_extrude.yy",},}, {"id":{"name":"node_3d_mesh_obj","path":"scripts/node_3d_mesh_obj/node_3d_mesh_obj.yy",},}, {"id":{"name":"node_3d_mesh_plane","path":"scripts/node_3d_mesh_plane/node_3d_mesh_plane.yy",},}, @@ -2172,6 +2173,7 @@ {"id":{"name":"s_node_3d_light_point","path":"sprites/s_node_3d_light_point/s_node_3d_light_point.yy",},}, {"id":{"name":"s_node_3d_meterial","path":"sprites/s_node_3d_meterial/s_node_3d_meterial.yy",},}, {"id":{"name":"s_node_3d_obj_combine","path":"sprites/s_node_3d_obj_combine/s_node_3d_obj_combine.yy",},}, + {"id":{"name":"s_node_3d_obj_export","path":"sprites/s_node_3d_obj_export/s_node_3d_obj_export.yy",},}, {"id":{"name":"s_node_3d_obj","path":"sprites/s_node_3d_obj/s_node_3d_obj.yy",},}, {"id":{"name":"s_node_3d_plane","path":"sprites/s_node_3d_plane/s_node_3d_plane.yy",},}, {"id":{"name":"s_node_3d_point_affector","path":"sprites/s_node_3d_point_affector/s_node_3d_point_affector.yy",},}, diff --git a/scripts/d3d_object/d3d_object.gml b/scripts/d3d_object/d3d_object.gml index d93ddab2f..0775a7292 100644 --- a/scripts/d3d_object/d3d_object.gml +++ b/scripts/d3d_object/d3d_object.gml @@ -5,19 +5,19 @@ global.VF_POS_COL = vertex_format_end(); vertex_format_begin(); - vertex_format_add_position_3d(); - vertex_format_add_normal(); - vertex_format_add_texcoord(); - vertex_format_add_color(); + vertex_format_add_position_3d(); // x y z // 12 + vertex_format_add_normal(); // x y z // 12 + vertex_format_add_texcoord(); // u v // 8 + vertex_format_add_color(); // r g b a // 4 global.VF_POS_NORM_TEX_COL = vertex_format_end(); global.VF_POS_NORM_TEX_COL_size = 36; #endregion function __3dObject() constructor { vertex = []; + VB = []; normal_vertex = []; object_counts = 1; - VB = []; NVB = noone; normal_draw_size = 0.2; diff --git a/scripts/node_3d_mesh_export/node_3d_mesh_export.gml b/scripts/node_3d_mesh_export/node_3d_mesh_export.gml new file mode 100644 index 000000000..f7ec8ba38 --- /dev/null +++ b/scripts/node_3d_mesh_export/node_3d_mesh_export.gml @@ -0,0 +1,173 @@ +function Node_3D_Mesh_Export(_x, _y, _group = noone) : Node(_x, _y, _group) constructor { + name = "Mesh Export"; + + inputs[| 0] = nodeValue("Mesh", self, JUNCTION_CONNECT.input, VALUE_TYPE.d3Mesh, noone) + .setVisible(true, true); + + inputs[| 1] = nodeValue("Paths", self, JUNCTION_CONNECT.input, VALUE_TYPE.path, "") + .setDisplay(VALUE_DISPLAY.path_save, { filter: "Obj (.obj)|*.obj" }) + .setVisible(true); + + inputs[| 2] = nodeValue("Export Texture", self, JUNCTION_CONNECT.input, VALUE_TYPE.boolean, true); + + inputs[| 3] = nodeValue("Invert UV", self, JUNCTION_CONNECT.input, VALUE_TYPE.boolean, false); + + input_display_list = [ 0, + ["Export", false], 1, 2, 3, + ]; + + + insp1UpdateTooltip = "Export"; + insp1UpdateIcon = [ THEME.sequence_control, 1, COLORS._main_value_positive ]; + + static onInspector1Update = function() { export(); } + + static export = function() { + var _mesh = getInputData(0); + var _path = getInputData(1); + var _mat = getInputData(2); + var _invv = getInputData(3); + + if(_mesh == noone) return; + if(!is_instanceof(_mesh, __3dObject)) return; + + var _vbs = _mesh.VB; + + var _mtlPath = filename_dir(_path) + "\\" + filename_name_only(_path) + ".mtl"; + var _mtlName = filename_name(_mtlPath); + var _mtl = "# Pixel Composer\n"; + var _obj = "# Pixel Composer\n"; + if(_mat) _obj += $"mtllib {_mtlName}\n"; + + for (var i = 0, n = array_length(_vbs); i < n; i++) { + var _vb = _vbs[i]; + + var _buffer = buffer_create_from_vertex_buffer(_vb, buffer_fixed, 1); + var _buffer_s = buffer_get_size(_buffer); + + _obj += $"o shape{i}\n"; + + if(_mat) { + var _matName = $"mat.{i}"; + var _material = array_safe_get_fast(_mesh.materials, i, 0); + + if(_material) { + _mtl += $"newmtl {_matName}\n"; + _obj += $"usemtl {_matName}\n"; + + if(is_surface(_material.surface)) { + var _surfPath = $"{filename_dir(_path)}\\{filename_name_only(_path)}_texture{i}.png"; + surface_save(_material.surface, _surfPath); + _mtl += $"map_Kd {_surfPath}\n"; + } + } + } + + var _obj_v = ""; + var _obj_vn = ""; + var _obj_vt = ""; + var _obj_f = ""; + + var _map_v = ds_map_create(); + var _map_vn = ds_map_create(); + var _map_vt = ds_map_create(); + var _map_f = ds_map_create(); + + switch(_mesh.VF) { + case global.VF_POS_NORM_TEX_COL : + var _format_s = global.VF_POS_NORM_TEX_COL_size; + var _vertex_s = floor(_buffer_s / _format_s); + var _ind = 0; + buffer_to_start(_buffer); + + var _idx_v = [ 0, 0, 0 ]; + var _idx_vn = [ 0, 0, 0 ]; + var _idx_vt = [ 0, 0, 0 ]; + + ds_map_clear(_map_v); + ds_map_clear(_map_vn); + ds_map_clear(_map_vt); + ds_map_clear(_map_f); + + repeat(_vertex_s) { + var _px = buffer_read(_buffer, buffer_f32); + var _py = buffer_read(_buffer, buffer_f32); + var _pz = buffer_read(_buffer, buffer_f32); + + var _nx = buffer_read(_buffer, buffer_f32); + var _ny = buffer_read(_buffer, buffer_f32); + var _nz = buffer_read(_buffer, buffer_f32); + + var _u = buffer_read(_buffer, buffer_f32); + var _v = buffer_read(_buffer, buffer_f32); + if(_invv) _v = 1. - _v; + + var _r = buffer_read(_buffer, buffer_s8); + var _g = buffer_read(_buffer, buffer_s8); + var _b = buffer_read(_buffer, buffer_s8); + var _a = buffer_read(_buffer, buffer_s8); + + var __v = $"v {string_format(_px, -1, 5)} {string_format(_py, -1, 5)} {string_format(_pz, -1, 5)}"; + var __vn = $"vn {string_format(_nx, -1, 5)} {string_format(_ny, -1, 5)} {string_format(_nz, -1, 5)}"; + var __vt = $"vt {string_format(_u, -1, 5)} {string_format(_v, -1, 5)}"; + + var _id_v, _id_vn, _id_vt; + + if(ds_map_exists(_map_v, __v)) { + _id_v = _map_v[? __v]; + } else { + _id_v = ds_map_size(_map_v) + 1; + _map_v[? __v] = _id_v; + _obj_v += $"{__v} \n"; + } + + if(ds_map_exists(_map_vn, __vn)) { + _id_vn = _map_vn[? __vn]; + } else { + _id_vn = ds_map_size(_map_vn) + 1; + _map_vn[? __vn] = _id_vn; + _obj_vn += $"{__vn} \n"; + } + + if(ds_map_exists(_map_vt, __vt)) { + _id_vt = _map_vt[? __vt]; + } else { + _id_vt = ds_map_size(_map_vt) + 1; + _map_vt[? __vt] = _id_vt; + _obj_vt += $"{__vt} \n"; + } + + _idx_v[_ind % 3] = _id_v; + _idx_vn[_ind % 3] = _id_vn; + _idx_vt[_ind % 3] = _id_vt; + + if(_ind % 3 == 2) _obj_f += $"f {_idx_v[0]}/{_idx_vt[0]}/{_idx_vn[0]} {_idx_v[1]}/{_idx_vt[1]}/{_idx_vn[1]} {_idx_v[2]}/{_idx_vt[2]}/{_idx_vn[2]}\n"; + _ind++; + } + break; + } + + ds_map_destroy(_map_v); + ds_map_destroy(_map_vn); + ds_map_destroy(_map_vt); + ds_map_destroy(_map_f); + + _obj += _obj_v + "\n"; + _obj += _obj_vn + "\n"; + _obj += _obj_vt + "\n"; + _obj += _obj_f + "\n"; + + buffer_delete(_buffer); + } + + file_text_write_all( _path, _obj); + if(_mat) file_text_write_all(_mtlPath, _mtl); + + var noti = log_message("EXPORT", $"Export model complete.", THEME.noti_icon_tick, COLORS._main_value_positive, false); + noti.path = filename_dir(_path); + noti.setOnClick(function() { shellOpenExplorer(self.path); }, "Open in explorer", THEME.explorer); + } + + static update = function() {} + +} \ No newline at end of file diff --git a/scripts/node_3d_mesh_export/node_3d_mesh_export.yy b/scripts/node_3d_mesh_export/node_3d_mesh_export.yy new file mode 100644 index 000000000..8443228f3 --- /dev/null +++ b/scripts/node_3d_mesh_export/node_3d_mesh_export.yy @@ -0,0 +1,13 @@ +{ + "$GMScript":"", + "%Name":"node_3d_mesh_export", + "isCompatibility":false, + "isDnD":false, + "name":"node_3d_mesh_export", + "parent":{ + "name":"3D", + "path":"folders/nodes/data/3D.yy", + }, + "resourceType":"GMScript", + "resourceVersion":"2.0", +} \ No newline at end of file diff --git a/scripts/node_3d_mesh_export/node_counter.yy b/scripts/node_3d_mesh_export/node_counter.yy new file mode 100644 index 000000000..10832a0b0 --- /dev/null +++ b/scripts/node_3d_mesh_export/node_counter.yy @@ -0,0 +1,12 @@ +{ + "isDnD": false, + "isCompatibility": false, + "parent": { + "name": "variable", + "path": "folders/nodes/data/variable.yy", + }, + "resourceVersion": "1.0", + "name": "node_counter", + "tags": [], + "resourceType": "GMScript", +} \ No newline at end of file diff --git a/scripts/node_registry/node_registry.gml b/scripts/node_registry/node_registry.gml index 24f92078d..6b352caf0 100644 --- a/scripts/node_registry/node_registry.gml +++ b/scripts/node_registry/node_registry.gml @@ -661,6 +661,7 @@ function __initNodes() { addNodeObject(d3d, "3D Scene", s_node_3d_scene, "Node_3D_Scene", [1, Node_3D_Scene],, "Combine multiple 3D objects into a single junction.").setVersion(11510); addNodeObject(d3d, "3D Repeat", s_node_3d_array, "Node_3D_Repeat", [1, Node_3D_Repeat],, "Repeat the same 3D mesh multiple times.").setVersion(11510); addNodeObject(d3d, "Transform 3D", s_node_image_transform_3d, "Node_3D_Transform_Image", [1, Node_3D_Transform_Image],, "Transform image in 3D space").setVersion(11600); + addNodeObject(d3d, "Mesh Export", s_node_3d_obj_export, "Node_3D_Mesh_Export", [1, Node_3D_Mesh_Export],, "Export 3D mesh as .obj file").setVersion(11740); ds_list_add(d3d, "Mesh"); addNodeObject(d3d, "3D Object", s_node_3d_obj, "Node_3D_Mesh_Obj", [0, Node_create_3D_Obj],, "Load .obj file from your computer as a 3D object.").setVersion(11510); diff --git a/sprites/s_node_3d_obj_export/1fe6f9a1-3122-4252-8359-48f017365596.png b/sprites/s_node_3d_obj_export/1fe6f9a1-3122-4252-8359-48f017365596.png new file mode 100644 index 000000000..3bf879844 Binary files /dev/null and b/sprites/s_node_3d_obj_export/1fe6f9a1-3122-4252-8359-48f017365596.png differ diff --git a/sprites/s_node_3d_obj_export/layers/1fe6f9a1-3122-4252-8359-48f017365596/c12fe317-c955-41e7-a157-32e91731ee30.png b/sprites/s_node_3d_obj_export/layers/1fe6f9a1-3122-4252-8359-48f017365596/c12fe317-c955-41e7-a157-32e91731ee30.png new file mode 100644 index 000000000..3bf879844 Binary files /dev/null and b/sprites/s_node_3d_obj_export/layers/1fe6f9a1-3122-4252-8359-48f017365596/c12fe317-c955-41e7-a157-32e91731ee30.png differ diff --git a/sprites/s_node_3d_obj_export/s_node_3d_obj_export.yy b/sprites/s_node_3d_obj_export/s_node_3d_obj_export.yy new file mode 100644 index 000000000..8b5a95dc6 --- /dev/null +++ b/sprites/s_node_3d_obj_export/s_node_3d_obj_export.yy @@ -0,0 +1,90 @@ +{ + "$GMSprite":"", + "%Name":"s_node_3d_obj_export", + "bboxMode":0, + "bbox_bottom":63, + "bbox_left":4, + "bbox_right":63, + "bbox_top":6, + "collisionKind":1, + "collisionTolerance":0, + "DynamicTexturePage":false, + "edgeFiltering":false, + "For3D":false, + "frames":[ + {"$GMSpriteFrame":"","%Name":"1fe6f9a1-3122-4252-8359-48f017365596","name":"1fe6f9a1-3122-4252-8359-48f017365596","resourceType":"GMSpriteFrame","resourceVersion":"2.0",}, + ], + "gridX":0, + "gridY":0, + "height":64, + "HTile":false, + "layers":[ + {"$GMImageLayer":"","%Name":"c12fe317-c955-41e7-a157-32e91731ee30","blendMode":0,"displayName":"default","isLocked":false,"name":"c12fe317-c955-41e7-a157-32e91731ee30","opacity":100.0,"resourceType":"GMImageLayer","resourceVersion":"2.0","visible":true,}, + ], + "name":"s_node_3d_obj_export", + "nineSlice":null, + "origin":4, + "parent":{ + "name":"3D", + "path":"folders/nodes/icons/3D.yy", + }, + "preMultiplyAlpha":false, + "resourceType":"GMSprite", + "resourceVersion":"2.0", + "sequence":{ + "$GMSequence":"", + "%Name":"s_node_3d_obj_export", + "autoRecord":true, + "backdropHeight":768, + "backdropImageOpacity":0.5, + "backdropImagePath":"", + "backdropWidth":1366, + "backdropXOffset":0.0, + "backdropYOffset":0.0, + "events":{ + "$KeyframeStore":"", + "Keyframes":[], + "resourceType":"KeyframeStore", + "resourceVersion":"2.0", + }, + "eventStubScript":null, + "eventToFunction":{}, + "length":1.0, + "lockOrigin":false, + "moments":{ + "$KeyframeStore":"", + "Keyframes":[], + "resourceType":"KeyframeStore", + "resourceVersion":"2.0", + }, + "name":"s_node_3d_obj_export", + "playback":1, + "playbackSpeed":30.0, + "playbackSpeedType":0, + "resourceType":"GMSequence", + "resourceVersion":"2.0", + "showBackdrop":true, + "showBackdropImage":false, + "timeUnits":1, + "tracks":[ + {"$GMSpriteFramesTrack":"","builtinName":0,"events":[],"inheritsTrackColour":true,"interpolation":1,"isCreationTrack":false,"keyframes":{"$KeyframeStore":"","Keyframes":[ + {"$Keyframe":"","Channels":{ + "0":{"$SpriteFrameKeyframe":"","Id":{"name":"1fe6f9a1-3122-4252-8359-48f017365596","path":"sprites/s_node_3d_obj_export/s_node_3d_obj_export.yy",},"resourceType":"SpriteFrameKeyframe","resourceVersion":"2.0",}, + },"Disabled":false,"id":"68e1dc21-23ce-44b4-ac1c-048da2846c01","IsCreationKey":false,"Key":0.0,"Length":1.0,"resourceType":"Keyframe","resourceVersion":"2.0","Stretch":false,}, + ],"resourceType":"KeyframeStore","resourceVersion":"2.0",},"modifiers":[],"name":"frames","resourceType":"GMSpriteFramesTrack","resourceVersion":"2.0","spriteId":null,"trackColour":0,"tracks":[],"traits":0,}, + ], + "visibleRange":null, + "volume":1.0, + "xorigin":32, + "yorigin":32, + }, + "swatchColours":null, + "swfPrecision":0.5, + "textureGroupId":{ + "name":"Default", + "path":"texturegroups/Default", + }, + "type":0, + "VTile":false, + "width":64, +} \ No newline at end of file