From 74e629d7b67eb17f360f8e781ff838dda81f4969 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zeron=20H=C3=B6shin?= <96698378+zHoeshin@users.noreply.github.com> Date: Sun, 30 Jun 2024 01:13:55 +0300 Subject: [PATCH] Update cosmic_reach_model_editor.js --- .../cosmic_reach_model_editor.js | 735 ++++++++++++++++-- 1 file changed, 669 insertions(+), 66 deletions(-) diff --git a/plugins/cosmic_reach_model_editor/cosmic_reach_model_editor.js b/plugins/cosmic_reach_model_editor/cosmic_reach_model_editor.js index ed464584..036b7c60 100644 --- a/plugins/cosmic_reach_model_editor/cosmic_reach_model_editor.js +++ b/plugins/cosmic_reach_model_editor/cosmic_reach_model_editor.js @@ -1,5 +1,5 @@ (() => { - let codec, export_action_minimized, export_action_maximized, import_action, dialog, originalJavaBlockCond, lastOccuranceOfSequenceInArray + let codec, export_action_block_minimized, export_action_block_maximized, import_action_block, dialog, originalJavaBlockCond, lastOccuranceOfSequenceInArray const id = "cosmic_reach_model_editor" const name = "Cosmic Reach Model Editor" const icon = "icon.png" @@ -10,7 +10,7 @@ author: "Z. Hoeshin", description: "Allows creating, editing, importing and exporting Cosmic Reach block models.", tags: ["Cosmic Reach"], - version: "1.2.0", + version: "1.3.0", min_version: "4.8.0", creation_date: "2024-04-19", variant: "both", @@ -61,22 +61,9 @@ Project.texture_height = 16 } }), - async export(options = {}) { - if (Object.keys(this.export_options).length) { - let result = await this.promptExportOptions(); - if (result === null) return; - } - Blockbench.export({ - resource_id: 'model', - type: this.name, - extensions: [this.extension], - name: this.fileName(), - startpath: this.startPath(), - content: this.compile(options), - custom_writer: isApp ? (a, b) => this.write(a, b) : null, - }, path => this.afterDownload(path)) - }, - compile(options={maximize: false}){ + compile(){ + let replacePostProcess = [] + let facenamesbb = ["up", "down", "north", "south", "east", "west"] let facenamescr = ["localPosY", "localNegY", "localNegZ", "localPosZ", "localPosX", "localNegX"] @@ -120,6 +107,16 @@ "cullFace": uvs.south[4].cullFace !== "", "texture": uvs.south[5]} } } + + replacePostProcess.push( + cube.localBounds, + cube.faces.localNegX, + cube.faces.localPosX, + cube.faces.localNegY, + cube.faces.localPosY, + cube.faces.localNegZ, + cube.faces.localPosZ, + ) for(let f = 0; f < 6; f++){ if(uvs[facenamesbb[f]][4].rotation > 0){ @@ -155,10 +152,11 @@ textures[name] = { "fileName": name } } - if(options.parent !== undefined){ - return JSON.stringify({"textures": textures, "parent": options.parent}, undefined, 4) - } - return JSON.stringify({"textures": textures, "cuboids": cuboids}, undefined, options.maximize ? 4 : undefined) + //JSON.stringify({"textures": textures, "cuboids": cuboids}, undefined, options.maximize ? 4 : undefined) + + + + return stringifyJSON({"textures": textures, "cuboids": cuboids}) }, parse(rawJSONstring, path, cuboidsOnly = false){ @@ -297,7 +295,7 @@ setUVforFace(cube, cuboid, facenamesbb[i], facenamescr[i]) cube.faces[facenamesbb[i]].texture = texture }catch(error){ - console.error(error) + } cube.faces[facenamesbb[i]].cullface = cuboid.faces[facenamescr[i]].cullFace ? facenamesbb[i] : "" cube.faces[facenamesbb[i]].tint = cuboid.faces[facenamescr[i]].ambientocclusion ? 0 : -1 @@ -313,9 +311,375 @@ } }) + codec_animation = new Codec("cosmic_reach_entity_animation_codec", { + name: "Cosmic Reach Entity Animation", + extension: "json", + load_filter: {type: "json", extensions: ["json"]}, + parse(rawJSONstring, path){ + let contents + if(typeof rawJSONstring === 'string'){ + contents = JSON.parse(rawJSONstring) + }else if(rawJSONstring instanceof Object && !(rawJSONstring instanceof Array)){ + contents = rawJSONstring + }else{ + throw "Unable to convert file data to Object" + } + + let bones = [] + function compileGroup(obj){ + bones[obj.name] = { + self: obj, + parent: null + } + for(let child of obj.children){ + if(child instanceof Group){ + compileGroup(child) + } + } + } + Outliner.root.forEach(obj => { + if (obj instanceof Group) { + compileGroup(obj); + } else if (obj instanceof Cube) { + //compileCube(obj) + } + }) + + + for(let animation_name of Object.keys(contents.animations)){ + let animation = contents.animations[animation_name] + let animationobj = new Animation({ + name: animation_name, + loop: animation.loop ? "loop" : "once", + length: animation.animation_length + }) + for(let bone_name of Object.keys(animation.bones)){ + let bone = animation.bones[bone_name] + let animator = animationobj.getBoneAnimator(bones[bone_name].self) + for(let channel_name of Object.keys(bone)){ + let channel = bone[channel_name] + if(channel instanceof Array){ + animator.addKeyframe({time: 0, channel: channel_name, data_points: [ + vectorFromArrayToObject(channel, true) + ]}) + }else if(channel instanceof Object){ + for(let timekey of Object.keys(channel)){ + let time = Number(timekey) + let keyframedata = channel[timekey] + + if(Array.isArray(keyframedata)){ + animator.addKeyframe({time: time, channel: channel_name, interpolation: "linear", data_points: [ + vectorFromArrayToObject(keyframedata, true) + ]}) + }else if(keyframedata instanceof Object){ + + if(keyframedata.pre){ + animator.addKeyframe({time: time, channel: channel_name, interpolation: "bezier", data_points: [ + vectorFromArrayToObject(keyframedata.pre, true) + ]}) + } + if(keyframedata.post){ + animator.addKeyframe({time: time, channel: channel_name, interpolation: "bezier", data_points: [ + vectorFromArrayToObject(keyframedata.post, true) + ]}) + } + } + } + } + } + } + + animationobj.add() + } + + }, + + compile(){ + let animations = {} + for(let anim of Project.animations){ + let animation = { + loop: anim.loop === "loop", + animation_length: anim.length + } + let bones = {} + for(let animatorEntry of Object.entries(anim.animators)){ + let animator = animatorEntry[1] + let bone = {} + if(animator.position.length == 1){ + if(animator.position[0].data_points.length == 1){ + if(animator.position[0].time == 0){ + let data_point = animator.position[0].data_points[0] + bone.position = [data_point.x, data_point.y, data_point.z].map((n) => Number(n)) + } + } + }else if(animator.position.length > 0){ + bone.position = {} + for(let keyframe of animator.position){ + if(!keyframe.data_points.length) continue + if(keyframe.interpolation === "bezier"){ + bone.position[keyframe.time.toString()] = { + "post": [keyframe.data_points[0].x, keyframe.data_points[0].y, keyframe.data_points[0].z].map((n) => Number(n)), + "lerp_mode": "catmullrom" + } + }else{ + bone.position[keyframe.time.toString()] = [keyframe.data_points[0].x, keyframe.data_points[0].y, keyframe.data_points[0].z].map((n) => Number(n)) + } + } + } + if(animator.rotation.length == 1){ + if(animator.rotation[0].data_points.length == 1){ + if(animator.rotation[0].time == 0){ + let data_point = animator.rotation[0].data_points[0] + bone.rotation = [data_point.x, data_point.y, data_point.z].map((n) => Number(n)) + } + } + }else if(animator.rotation.length > 0){ + bone.rotation = {} + for(let keyframe of animator.rotation){ + if(!keyframe.data_points.length) continue + if(keyframe.interpolation === "bezier"){ + bone.rotation[keyframe.time.toString()] = { + "post": [keyframe.data_points[0].x, keyframe.data_points[0].y, keyframe.data_points[0].z].map((n) => Number(n)), + "lerp_mode": "catmullrom" + } + }else{ + bone.rotation[keyframe.time.toString()] = [keyframe.data_points[0].x, keyframe.data_points[0].y, keyframe.data_points[0].z].map((n) => Number(n)) + } + } + } + if(animator.scale.length == 1){ + if(animator.scale[0].data_points.length == 1){ + if(animator.scale[0].time == 0){ + let data_point = animator.scale[0].data_points[0] + bone.scale = [data_point.x, data_point.y, data_point.z].map((n) => Number(n)) + } + } + }else if(animator.scale.length > 0){ + bone.scale = {} + for(let keyframe of animator.scale){ + if(!keyframe.data_points.length) continue + if(keyframe.interpolation === "bezier"){ + bone.scale[keyframe.time.toString()] = { + "post": [keyframe.data_points[0].x, keyframe.data_points[0].y, keyframe.data_points[0].z].map((n) => Number(n)), + "lerp_mode": "catmullrom" + } + }else{ + bone.scale[keyframe.time.toString()] = [keyframe.data_points[0].x, keyframe.data_points[0].y, keyframe.data_points[0].z].map((n) => Number(n)) + } + } + } + if(bone.position){ + bones[animator.group.name] = {position: bone.position} + } + if(bone.rotation){ + if(bones[animator.group.name] === undefined){ + bones[animator.group.name] = {} + } + if(bones[animator.group.name].rotation === undefined){ + bones[animator.group.name].rotation = null + } + bones[animator.group.name].rotation = bone.rotation + } + if(bone.scale){ + if(bones[animator.group.name] === undefined){ + bones[animator.group.name] = {} + } + if(bones[animator.group.name].scale === undefined){ + bones[animator.group.name].scale = null + } + bones[animator.group.name].scale = bone.scale + } + } + animation.bones = bones + animations[anim.name] = animation + } + return stringifyJSON({animations: animations}) + } + }) + + codec_entity = new Codec("cosmic_reach_entity_model_codec", { + name: "Cosmic Reach Entity", + extension: "json", + remember: false, + load_filter: {type: "json", extensions: ["json"], + condition: (model) => { + return model.id && model.texture_width && model.texture_height && model.bones + }}, + format: new ModelFormat("cosmic_reach_entity_model", { + id: "cosmic_reach_entity_model", + icon: icon64, + name: "Cosmic Reach Entity Model", + description: "Entiy model format used by the game Cosmic Reach", + show_on_start_screen: true, + target: ["json"], + + vertex_color_ambient_occlusion: true, + rotate_cubes: true, + rotation_limit: false, + rotation_snap: true, + uv_rotation: false, + box_uv: true, + java_face_properties: true, + centered_grid: true, + edit_mode: true, + rotate_cubes: true, + box_uv: true, + single_texture: true, + bone_rig: true, + centered_grid: true, + animated_textures: true, + animation_files: true, + animation_mode: true, + animation_controllers: true, + bone_binding_expression: true, + locators: true, + + new() { + newProject(this) + } + }), + compile(){ + let bones = [] + + function compileCube(obj){ + let cube = { + origin: obj.from, + size: [obj.to[0] - obj.from[0], obj.to[1] - obj.from[1], obj.to[2] - obj.from[2]], + uv: obj.uv_offset + } + if(!vectorIsEqualToVector(obj.origin, [0, 0, 0])){ + cube.pivot = obj.origin + } + if(!vectorIsEqualToVector(obj.rotation, [0, 0, 0])){ + cube.rotation = obj.rotation + } + if(obj.inflate != 0){ + cube.inflate = obj.inflate + } + return cube + } + function compileGroup(obj){ + /*group.children.forEach(obj => { + if (obj instanceof Group) { + compileGroup(obj); + } else if (obj instanceof Cube) { + compileCube(obj) + } + })*/ + let newBone = { + name: obj.name, + pivot: obj.origin + } + if((obj.rotation[0] !== 0)||(obj.rotation[1] !== 0)||(obj.rotation[2] !== 0)){ + newBone.rotation = obj.rotation + } + + if(obj.parent != "root"){ + newBone.parent = obj.parent.name + } + for(let child of obj.children){ + if(child instanceof Group){ + compileGroup(child) + }else if(child instanceof Cube){ + if(newBone.cubes === undefined){ + newBone.cubes = [] + } + newBone.cubes.push(compileCube(child)) + } + } + bones.push(newBone) + } + + Outliner.root.forEach(obj => { + if (obj instanceof Group) { + compileGroup(obj); + } else if (obj instanceof Cube) { + //compileCube(obj) + } + }) + + // + return stringifyJSON({id: name, texture_width: Project.texture_width, texture_height: Project.texture_height, bones: bones}) + }, + + parse(rawJSONstring, path, cuboidsOnly = false){ + let data + if(typeof rawJSONstring === 'string'){ + data = JSON.parse(rawJSONstring) + }else if(rawJSONstring instanceof Object && !(rawJSONstring instanceof Array)){ + data = rawJSONstring + }else{ + throw "Unable to convert file data to Object" + } + + Project.texture_width = data.texture_width || 16 + Project.texture_height = data.texture_height || 16 + + Project.name = data.id || "" + + bones = {} + for(let bone of data.bones){ + let group = new Group((({ cubes, parent, ...o }) => o)(bone)).init() + group.origin = bone.pivot + if(bone.cubes){ + for(let cube of bone.cubes){ + let newCube = new Cube({ + uv_offset: cube.uv, + from: cube.origin, + size: cube.size, + rotation: cube.rotation, + origin: cube.pivot, + inflate: cube.inflate + }) + newCube.addTo(group).init() + } + } + bones[bone.name] = {"self": group, "parent": bone.parent} + } + for(let bone of Object.keys(bones)){ + b = bones[bone] + if(b.parent){ + b.self.addTo(bones[b.parent].self) + }else{ + b.self.addTo("root") + } + } + + let patharr = path.split(/[\\\/]/g) + + + //patharr = patharr.slice(0, patharr.length - 1) + + // + + let root = lastOccuranceOfSequenceInArray(patharr, ["models", "entities"]) + + let animpatharr = [...patharr.slice(undefined, root - 1), "animations", ...patharr.slice(root)] + animpatharr[animpatharr.length - 1] = animpatharr[animpatharr.length - 1].replace(/\.json$/gi, ".animation.json").replace(/^model_/gi, "") + let animpath = animpatharr.join("/") + Blockbench.read(animpath, { + extensions: ['json'], + type: 'Cosmic Reach Entity Model', + readtype: 'text', + resource_id: 'json' + }, files => { + try{ + let contents = JSON.parse(files[0].content) + codec_animation.parse(contents, animpath) + }catch(error){ + dialog.lines = `
+

Unable to import animations of the model.

+

${error}

+
`.split("\n") + dialog.show() + } + }) + + } + }) - import_action = new Action('import_cosmic_reach_model', { - name: 'Import Cosmic Reach Model', + import_action_block = new Action('import_cosmic_reach_model', { + name: 'Import Cosmic Reach Block Model', description: '', icon: icon64, category: 'file', @@ -340,14 +704,55 @@ } }) - export_action_minimized = new Action('export_cosmic_reach_model_minimized', { - name: 'Export Cosmic Reach Model (minimized)', + export_action_block = new Action('export_cosmic_reach_model', { + name: 'Export Cosmic Reach Block Model', description: '', icon: icon64, category: 'file', + /*side_menu: new Menu("export_cosmic_reach_model_sidemenu", [ + new Action('export_cosmic_reach_model', { + name: 'Export minimized', + description: '', + icon: icon64, + category: 'file', + structure: [ + + ], + click() { + try{ + codec.export({maximize: false, parent: undefined}); + }catch(error){ + dialog.lines = `
+

Unable to export file.

+

${error}

+
`.split("\n") + dialog.show() + } + } + }), + new Action('export_cosmic_reach_model_maximized', { + name: 'Export maximized', + description: '', + icon: icon64, + category: 'file', + click() { + try{ + codec.export({maximize: true, parent: undefined}); + }catch(error){ + dialog.lines = `
+

Unable to export file.

+

${error}

+
`.split("\n") + dialog.show() + } + } + }), + + + ]),*/ click() { try{ - codec.export({maximize: false, parent: undefined}); + codec.export({maximize: settings.cosmic_reach_maximize_block_models.value, parent: undefined}); }catch(error){ dialog.lines = `

Unable to export file.

@@ -357,43 +762,111 @@ } } }) - export_action_maximized = new Action('export_cosmic_reach_model_maximized', { - name: 'Export Cosmic Reach Model (maximized)', - description: '', - icon: icon64, - category: 'file', - click() { - try{ - codec.export({maximize: true, parent: undefined}); - }catch(error){ - dialog.lines = `
-

Unable to export file.

-

${error}

-
`.split("\n") - dialog.show() + //export_action_block_maximized = + export_action_block_aschild = new Action('export_cosmic_reach_model_aschild', { + name: 'Export Cosmic Reach Block Child Model', + description: '', + icon: icon64, + category: 'file', + click() { + try{ + new Dialog("cosmic_reach_model_exportaschildmodeldialog", { + id: "cosmic_reach_model_dialog_aschild", + title: "Export model as a child", + form: { + name: { + label: "Parent name", + value: Project._name + } + }, + onConfirm: result => { + codec.export({maximize: settings.cosmic_reach_maximize_block_children_models.value, parent: result.name}); + } + }).show() + }catch(error){ + dialog.lines = `
+

Unable to export file.

+

${error}

+
`.split("\n") + dialog.show() + } } - } + }) + + MenuBar.addAction(import_action_block, 'file.import') + MenuBar.addAction(export_action_block, 'file.export') + /*MenuBar.addAction(export_action_block_minimized, 'file.export') + MenuBar.addAction(export_action_block_maximized, 'file.export')*/ + MenuBar.addAction(export_action_block_aschild, 'file.export') + + import_action_entity = new Action('import_cosmic_reach_entity_model', { + name: 'Import Cosmic Reach Entity Model', + description: '', + icon: icon64, + category: 'file', + click() { + Blockbench.import({ + extensions: ['json'], + type: 'Cosmic Reach Model', + readtype: 'text', + resource_id: 'json' + }, files => { + /*try{*/ + codec_entity.parse(files[0].content, files[0].path); + Canvas.updateAll() + /*}catch(error){ + dialog.lines = `
+

Unable to import file.

+

${error}

+
`.split("\n") + dialog.show() + }*/ }) - export_action_aschild = new Action('export_cosmic_reach_model_aschild', { - name: 'Export Cosmic Reach Model (Child)', + } + }) + export_action_entity = new Action('export_cosmic_reach_entity_model', { + name: 'Export Cosmic Reach Entity Model', description: '', icon: icon64, category: 'file', + /*children: side_menu: new Menu("export_cosmic_reach_entity_model_side_menu",[new Action('export_cosmic_reach_entity_model_minimized', { + name: 'Export minimized', + description: '', + icon: icon64, + category: 'file', + side_menu: new Menu("export_cosmic_reach_entity_model_side_menu",[]), + click() { + try{ + codec_entity.export({maximize: true}); + }catch(error){ + dialog.lines = `
+

Unable to export file.

+

${error}

+
`.split("\n") + dialog.show() + } + } + }),new Action('export_cosmic_reach_entity_model_maximized', { + name: 'Export maximized', + description: '', + icon: icon64, + category: 'file', + side_menu: new Menu("export_cosmic_reach_entity_model_side_menu",[]), + click() { + try{ + codec_entity.export({maximize: false}); + }catch(error){ + dialog.lines = `
+

Unable to export file.

+

${error}

+
`.split("\n") + dialog.show() + } + } + }),]),*/ click() { try{ - new Dialog("cosmic_reach_model_exportaschildmodeldialog", { - id: "cosmic_reach_model_dialog_aschild", - title: "Export model as a child", - form: { - name: { - label: "Parent name", - value: Project._name - } - }, - onConfirm: result => { - codec.export({maximize: true, parent: result.name}); - } - }).show() + codec_entity.export({maximize: settings.cosmic_reach_maximize_entity_models.value}); }catch(error){ dialog.lines = `

Unable to export file.

@@ -404,11 +877,55 @@ } }) - MenuBar.addAction(import_action, 'file.import') - MenuBar.addAction(export_action_minimized, 'file.export') - MenuBar.addAction(export_action_maximized, 'file.export') - MenuBar.addAction(export_action_aschild, 'file.export') + MenuBar.addAction(import_action_entity, 'file.import') + MenuBar.addAction(export_action_entity, 'file.export') + import_action_entity_animation = new Action('import_cosmic_reach_entity_animation', { + name: 'Import Cosmic Reach Entity Animation', + description: '', + icon: icon64, + category: 'file', + click() { + Blockbench.import({ + extensions: ['json'], + type: 'Cosmic Reach Animation', + readtype: 'text', + resource_id: 'json' + }, files => { + try{ + codec_animation.parse(files[0].content, files[0].path); + Canvas.updateAll() + }catch(error){ + dialog.lines = `
+

Unable to import file.

+

${error}

+
`.split("\n") + dialog.show() + } + }) + } + }) + export_action_entity_animation = new Action('export_cosmic_reach_entity_animation', { + name: 'Export Cosmic Reach Entity Animation', + description: '', + icon: icon64, + category: 'file', + click() { + try{ + codec_animation.export({maximize: settings.cosmic_reach_maximize_entity_animations.value}); + }catch(error){ + dialog.lines = `
+

Unable to export file.

+

${error}

+
`.split("\n") + dialog.show() + } + } + }) + + + MenuBar.addAction(import_action_entity_animation, 'file.import') + MenuBar.addAction(export_action_entity_animation, 'file.export') lastOccuranceOfSequenceInArray = (array, sequence) => { let count = 0 @@ -423,12 +940,98 @@ } return -1 } + vectorFromArrayToObject = (vectorArray, isString = false) => { + return isString ? {x: vectorArray[0].toString(), y: vectorArray[1].toString(), z: vectorArray[2].toString()} : {x: vectorArray[0], y: vectorArray[1], z: vectorArray[2]} + } + + vectorIsEqualToVector = (vectorA, vectorB) => { + return (vectorA[0] == vectorB[0]) && (vectorA[1] == vectorB[1]) && (vectorA[2] == vectorB[2]) + } + + function stringifyJSON(obj, exclude = [], space = "\t", excluder = (obj) => { + if(Array.isArray(obj)){ + return obj.every(Number.isFinite) + }else if(obj instanceof Object){ + return obj.uv !== undefined + } + return false + }) { + let recur = (obj, spacing, inarray, islastinarray = false) => { + let txt = ''; + + if (inarray) { + if (Array.isArray(obj)) { + txt += '['; + + for(let i=0;i