From 66f7ec2b44fc0c1d33c8a9b0fc8e510980180a59 Mon Sep 17 00:00:00 2001 From: JannisX11 Date: Mon, 20 Sep 2021 18:45:02 +0200 Subject: [PATCH] Add UV rotate tool Improve array export of JSON compiler Stop texture animations playing when switching tab Fix duplicate keybinding from add mesh button Improve transform space normal calculation Fix interface issues Add "Instance" property type --- assets/rotate_cursor.png | Bin 0 -> 11933 bytes css/general.css | 6 ++ css/panels.css | 31 ++++++- js/io/formats/bbmodel.js | 2 +- js/io/io.js | 10 +-- js/io/project.js | 1 + js/outliner/cube.js | 12 +-- js/outliner/mesh.js | 24 ++++-- js/property.js | 9 +- js/texturing/textures.js | 8 +- js/texturing/uv.js | 180 ++++++++++++++++++++++++++++++++++----- package-lock.json | 32 +++---- package.json | 2 +- 13 files changed, 250 insertions(+), 67 deletions(-) create mode 100644 assets/rotate_cursor.png diff --git a/assets/rotate_cursor.png b/assets/rotate_cursor.png new file mode 100644 index 0000000000000000000000000000000000000000..36c6b2b7b1d125564e214bbfe831bdadb33a9212 GIT binary patch literal 11933 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 QOqK!Em35RV6|6)452O=?-T(jq literal 0 HcmV?d00001 diff --git a/css/general.css b/css/general.css index c11e1e9e..5abbd590 100644 --- a/css/general.css +++ b/css/general.css @@ -328,6 +328,12 @@ display: inline-block; margin-top: 4px; } + .tool:active .icon { + padding-top: 1px; + } + .tool:active .icon.fa_big { + padding-top: 2px; + } .tool.enabled { border-bottom: 3px solid var(--color-accent); diff --git a/css/panels.css b/css/panels.css index f638d847..a337510b 100644 --- a/css/panels.css +++ b/css/panels.css @@ -1182,7 +1182,7 @@ background-color: rgba(50, 70, 240, 0.14); } .cube_box_uv:hover > div { - border-color: var(--color-light); + border-color: white; z-index: 3; } .cube_uv_face { @@ -1198,12 +1198,12 @@ color: var(--color-subtle_text); } .cube_uv_face:hover { - border-color: var(--color-light); + border-color: white; background-color: rgba(50, 70, 240, 0.3); z-index: 3; } .cube_uv_face.selected:not(.unselected) { - border-color: var(--color-light); + border-color: white; z-index: 4; box-shadow: 0 0 4px #000000cc; } @@ -1273,7 +1273,7 @@ stroke-width: 2px; } .mesh_uv_face:hover polygon { - stroke: var(--color-light); + stroke: white; } .mesh_uv_face.selected polygon { stroke: var(--color-accent); @@ -1303,6 +1303,29 @@ text-align: center; } + .main_corner { + position: absolute; + } + .main_corner::after { + content: ""; + display: block; + margin: -2px; + height: 11px; + width: 11px; + border: 1px solid white; + } + .main_corner.selected::after { + border-color: var(--color-accent); + } + .uv_rotate_field { + position: absolute; + width: 15px; + height: 15px; + bottom: 6px; + right: 6px; + cursor: url('../assets/rotate_cursor.png') 9 9, auto; + } + .panel .bar.next_to_title { margin-top: -34px; margin-right: 32px; diff --git a/js/io/formats/bbmodel.js b/js/io/formats/bbmodel.js index a2c8ecda..d4bb1ec4 100644 --- a/js/io/formats/bbmodel.js +++ b/js/io/formats/bbmodel.js @@ -1,6 +1,6 @@ (function() { -let FORMATV = '3.6'; +let FORMATV = '4.0'; function processHeader(model) { if (!model.meta) { diff --git a/js/io/io.js b/js/io/io.js index 80e03844..1307f92e 100644 --- a/js/io/io.js +++ b/js/io/io.js @@ -392,21 +392,21 @@ function compileJSON(object, options) { //Number o = (Math.round(o*100000)/100000).toString() out += o - } else if (typeof o === 'object' && o instanceof Array) { + } else if (o instanceof Array) { //Array - var has_content = false + let has_content = false + let has_objects = !!o.find(item => typeof item === 'object'); out += '[' for (var i = 0; i < o.length; i++) { var compiled = handleVar(o[i], tabs+1) if (compiled) { - var breaks = typeof o[i] === 'object' if (has_content) {out += ',' + (breaks || options.small?'':' ')} - if (breaks) {out += newLine(tabs)} + if (has_objects) {out += newLine(tabs)} out += compiled has_content = true } } - if (typeof o[o.length-1] === 'object') {out += newLine(tabs-1)} + if (has_objects) {out += newLine(tabs-1)} out += ']' } else if (typeof o === 'object') { //Object diff --git a/js/io/project.js b/js/io/project.js index edcda64b..0d46cd06 100644 --- a/js/io/project.js +++ b/js/io/project.js @@ -254,6 +254,7 @@ class ModelProject { } }) + if (TextureAnimator.isPlaying) TextureAnimator.stop(); this.selected = false; Painter.current = {}; scene.remove(this.model_3d); diff --git a/js/outliner/cube.js b/js/outliner/cube.js index 762dd869..ec80177c 100644 --- a/js/outliner/cube.js +++ b/js/outliner/cube.js @@ -873,9 +873,15 @@ new NodePreviewController(Cube, { let {mesh} = cube; let indices = []; + let j = 0; + mesh.geometry.faces = []; + mesh.geometry.clearGroups(); Canvas.face_order.forEach((fkey, i) => { if (cube.faces[fkey].texture !== null) { indices.push(0 + i*4, 2 + i*4, 1 + i*4, 2 + i*4, 3 + i*4, 1 + i*4); + mesh.geometry.addGroup(j*6, 6, j) + mesh.geometry.faces.push(fkey) + j++; } }) mesh.geometry.setIndex(indices) @@ -899,11 +905,7 @@ new NodePreviewController(Cube, { } else { var materials = [] Canvas.face_order.forEach(function(face) { - - if (cube.faces[face].texture === null) { - materials.push(Canvas.transparentMaterial) - - } else { + if (cube.faces[face].texture !== null) { var tex = cube.faces[face].getTexture() if (tex && tex.uuid) { materials.push(Project.materials[tex.uuid]) diff --git a/js/outliner/mesh.js b/js/outliner/mesh.js index 5b8bfec6..28625ffa 100644 --- a/js/outliner/mesh.js +++ b/js/outliner/mesh.js @@ -279,15 +279,30 @@ class Mesh extends OutlinerElement { return faces; } getSelectionRotation() { - let [face] = this.getSelectedFaces().map(fkey => this.faces[fkey]); - if (face) { - let normal = face.getNormal(true) + let faces = this.getSelectedFaces().map(fkey => this.faces[fkey]); + if (!faces[0]) { + let selected_vertices = this.getSelectedVertices(); + this.forAllFaces((face) => { + if (face.vertices.find(vkey => selected_vertices.includes(vkey))) { + faces.push(face); + } + }) + } + if (faces[0]) { + let normal = [0, 0, 0]; + faces.forEach(face => normal.V3_add(face.getNormal(true))) + normal.V3_divide(faces.length); var y = Math.atan2(normal[0], normal[2]); var x = Math.atan2(normal[1], Math.sqrt(Math.pow(normal[0], 2) + Math.pow(normal[2], 2))); return new THREE.Euler(-x, y, 0, 'YXZ'); } } + forAllFaces(cb) { + for (let fkey in this.faces) { + cb(this.faces[fkey], fkey); + } + } transferOrigin(origin, update = true) { if (!this.mesh) return; var q = new THREE.Quaternion().copy(this.mesh.quaternion); @@ -814,7 +829,7 @@ BARS.defineActions(function() { }}, diameter: {label: 'dialog.add_primitive.diameter', type: 'number', value: 16}, height: {label: 'dialog.add_primitive.height', type: 'number', value: 8, condition: ({shape}) => ['cylinder', 'cone', 'cube', 'pyramid', 'tube'].includes(shape)}, - sides: {label: 'dialog.add_primitive.sides', type: 'number', value: 16, condition: ({shape}) => ['cylinder', 'cone', 'circle', 'torus', 'sphere', 'tube'].includes(shape)}, + sides: {label: 'dialog.add_primitive.sides', type: 'number', value: 12, condition: ({shape}) => ['cylinder', 'cone', 'circle', 'torus', 'sphere', 'tube'].includes(shape)}, minor_diameter: {label: 'dialog.add_primitive.minor_diameter', type: 'number', value: 4, condition: ({shape}) => ['torus', 'tube'].includes(shape)}, minor_sides: {label: 'dialog.add_primitive.minor_sides', type: 'number', value: 8, condition: ({shape}) => ['torus'].includes(shape)}, }, @@ -1066,7 +1081,6 @@ BARS.defineActions(function() { new Action('add_mesh', { icon: 'fa-gem', category: 'edit', - keybind: new Keybind({key: 'n', ctrl: true}), condition: () => (Modes.edit && Format.meshes), click: function () { add_mesh_dialog.show(); diff --git a/js/property.js b/js/property.js index 0d414265..5465964e 100644 --- a/js/property.js +++ b/js/property.js @@ -5,8 +5,6 @@ class Property { } target_class.properties[name] = this; - let scope = this; - this.class = target_class; this.name = name; this.type = type; @@ -20,6 +18,7 @@ class Property { case 'number': this.default = 0; break; case 'boolean': this.default = false; break; case 'array': this.default = []; break; + case 'instance': this.default = null; break; case 'vector': this.default = [0, 0, 0]; break; case 'vector2': this.default = [0, 0]; break; } @@ -30,6 +29,7 @@ class Property { case 'number': this.isNumber = true; break; case 'boolean': this.isBoolean = true; break; case 'array': this.isArray = true; break; + case 'instance': this.isInstance = true; break; case 'vector': this.isVector = true; break; case 'vector2': this.isVector2 = true; break; } @@ -87,6 +87,11 @@ class Property { instance[this.name].replace(data[this.name]); } } + else if (this.isInstance) { + if (typeof data[this.name] === 'object') { + instance[this.name] =data[this.name]; + } + } } copy(instance, target) { if (!Condition(this.condition, instance)) return; diff --git a/js/texturing/textures.js b/js/texturing/textures.js index 35a244c2..c28ad30a 100644 --- a/js/texturing/textures.js +++ b/js/texturing/textures.js @@ -442,9 +442,6 @@ class Texture { this.source = dataUrl; this.img.src = dataUrl; this.updateMaterial(); - if (this == UVEditor.texture) { - UVEditor.img.src = dataUrl; - }; if (open_dialog == 'UVEditor') { for (var key in UVEditor.editors) { var editor = UVEditor.editors[key]; @@ -654,7 +651,7 @@ class Texture { TextureAnimator.updateButton() hideDialog() if (UVEditor.texture == this) { - UVEditor.displayTexture(); + UVEditor.vue.updateTexture(); } BARS.updateConditions() Undo.finishEdit('Remove texture', {textures: []}) @@ -1381,9 +1378,6 @@ TextureAnimator = { $(`.texture[texid="${tex.uuid}"]`).find('img').css('margin-top', (tex.currentFrame*-48)+'px'); maxFrame = Math.max(maxFrame, tex.currentFrame); }) - if (animated_textures.includes(UVEditor.texture)) { - UVEditor.img.style.objectPosition = `0 -${UVEditor.texture.currentFrame * UVEditor.inner_height}px`; - } Cube.all.forEach(cube => { var update = false for (var face in cube.faces) { diff --git a/js/texturing/uv.js b/js/texturing/uv.js index 5c8d9ebe..5733e9ad 100644 --- a/js/texturing/uv.js +++ b/js/texturing/uv.js @@ -24,7 +24,6 @@ const UVEditor = { grid: 1, max_zoom: 16, auto_grid: true, - texture: false, panel: null, sliders: {}, @@ -373,6 +372,9 @@ const UVEditor = { get selected_faces() { return this.vue.selected_faces; }, + get texture() { + return this.vue.texture; + }, getPixelSize() { if (Project.box_uv) { return this.inner_width/Project.texture_width @@ -656,6 +658,18 @@ const UVEditor = { scope.getFaces(obj, event).forEach(function(side) { var uv = obj.faces[side].uv_size; obj.faces[side].uv_size = [uv[1], uv[0]]; + if (uv[0] < 0) { + obj.faces[side].uv[0] += uv[0]; + obj.faces[side].uv[2] += uv[0]; + obj.faces[side].uv[1] -= uv[0]; + obj.faces[side].uv[3] -= uv[0]; + } + if (uv[1] < 0) { + obj.faces[side].uv[1] += uv[1]; + obj.faces[side].uv[3] += uv[1]; + obj.faces[side].uv[0] -= uv[1]; + obj.faces[side].uv[2] -= uv[1]; + } }) obj.autouv = 0; Canvas.updateUV(obj); @@ -896,11 +910,10 @@ const UVEditor = { this.message('uv_editor.mirrored') this.loadData() }, - applyAll(event) { - var scope = this; + applyAll() { this.forCubes(obj => { - UVEditor.cube_faces.forEach(function(side) { - $.extend(true, obj.faces[side], obj.faces[scope.face]) + UVEditor.cube_faces.forEach(side => { + obj.faces[side].extend(obj.faces[this.selected_faces[0]]) }) obj.autouv = 0 }) @@ -1582,14 +1595,14 @@ Interface.definePanels(function() { } } if (texture === null) { - this.texture = UVEditor.texture = null; + this.texture = null; } else if (texture instanceof Texture) { this.texture = texture; if (!Project.box_uv && UVEditor.auto_grid) { UVEditor.grid = texture.width / Project.texture_width; } } else { - this.texture = UVEditor.texture = 0; + this.texture = 0; } }, updateMouseCoords(event) { @@ -1776,7 +1789,7 @@ Interface.definePanels(function() { } } }, - drag({event, onDrag, onEnd, onAbort}) { + drag({event, onDrag, onEnd, onAbort, snap}) { if (event.which == 2 || event.which == 3) return; let scope = this; @@ -1784,13 +1797,21 @@ Interface.definePanels(function() { let last_pos = [0, 0]; function drag(e1) { - let snap = UVEditor.grid / canvasGridSize(e1.shiftKey || Pressing.overrides.shift, e1.ctrlOrCmd || Pressing.overrides.ctrl); + if (snap == undefined) { + let snap = UVEditor.grid / canvasGridSize(e1.shiftKey || Pressing.overrides.shift, e1.ctrlOrCmd || Pressing.overrides.ctrl); + + let step_x = (scope.inner_width / UVEditor.getResolution(0) / snap); + let step_y = (scope.inner_height / UVEditor.getResolution(1) / snap); - let step_x = (scope.inner_width / UVEditor.getResolution(0) / snap); - let step_y = (scope.inner_height / UVEditor.getResolution(1) / snap); - - pos[0] = Math.round((e1.clientX - event.clientX) / step_x) / snap; - pos[1] = Math.round((e1.clientY - event.clientY) / step_y) / snap; + pos[0] = Math.round((e1.clientX - event.clientX) / step_x) / snap; + pos[1] = Math.round((e1.clientY - event.clientY) / step_y) / snap; + } else { + let step_x = snap + let step_y = snap + + pos[0] = Math.round((e1.clientX - event.clientX) / step_x) / snap; + pos[1] = Math.round((e1.clientY - event.clientY) / step_y) / snap; + } if (pos[0] != last_pos[0] || pos[1] != last_pos[1]) { onDrag(pos[0] - last_pos[0], pos[1] - last_pos[1], e1) @@ -1904,6 +1925,113 @@ Interface.definePanels(function() { } }) }, + rotateFace(face_key, event) { + if (event.which == 2 || event.which == 3) return; + event.stopPropagation(); + let scope = this; + let elements = this.mappable_elements; + Undo.initEdit({elements, uv_only: true}) + + let face_center = [0, 0]; + let points = 0; + elements.forEach(element => { + this.selected_faces.forEach(fkey => { + let face = element.faces[fkey]; + if (!face) return; + if (element instanceof Cube) { + face_center[0] += face.uv[0] + face.uv[2]; + face_center[1] += face.uv[1] + face.uv[3]; + points += 2; + } else if (element instanceof Mesh) { + face.vertices.forEach(vkey => { + if (!face.uv[vkey]) return; + face_center[0] += face.uv[vkey][0]; + face_center[1] += face.uv[vkey][1]; + points += 1; + }) + } + }) + }) + face_center.forEach((v, i) => face_center[i] = v / points); + + let offset = $(UVEditor.vue.$refs.frame).offset(); + let center_on_screen = [ + face_center[0] * UVEditor.getPixelSize() + offset.left, + face_center[1] * UVEditor.getPixelSize() + offset.top, + ] + + let angle = 0; + let last_angle; + let snap = elements[0] instanceof Cube ? 90 : 1; + function drag(e1) { + + angle = Math.atan2( + (e1.clientY - center_on_screen[1]), + (e1.clientX - center_on_screen[0]), + ) + angle = Math.round(Math.radToDeg(angle) / snap) * snap; + if (last_angle == undefined) last_angle = angle; + if (Math.abs(angle - last_angle) > 300) last_angle = angle; + + if (angle != last_angle) { + + elements.forEach(element => { + if (element instanceof Cube && Format.uv_rotation) { + scope.selected_faces.forEach(key => { + if (element.faces[key]) { + element.faces[key].rotation += 90 * Math.sign(last_angle - angle); + console.log(element.faces[key].rotation, Math.sign(last_angle - angle)) + if (element.faces[key].rotation == 360) element.faces[key].rotation = 0; + if (element.faces[key].rotation < 0) element.faces[key].rotation += 360; + console.log(element.faces[key].rotation) + console.log('-----') + } + }) + + } else if (element instanceof Mesh) { + scope.selected_faces.forEach(fkey => { + let face = element.faces[fkey]; + if (!face) return; + face.vertices.forEach(vkey => { + if (!face.uv[vkey]) return; + let sin = Math.sin(Math.degToRad(angle - last_angle)); + let cos = Math.cos(Math.degToRad(angle - last_angle)); + face.uv[vkey][0] -= face_center[0]; + face.uv[vkey][1] -= face_center[1]; + face.uv[vkey][0] = (face.uv[vkey][0] * cos - face.uv[vkey][1] * sin) + face.uv[vkey][1] = (face.uv[vkey][0] * sin + face.uv[vkey][1] * cos) + face.uv[vkey][0] += face_center[0]; + face.uv[vkey][1] += face_center[1]; + }) + }) + } + }) + UVEditor.turnMapping() + + last_angle = angle; + UVEditor.displaySliders(); + UVEditor.loadData(); + UVEditor.vue.$forceUpdate(); + Canvas.updateView({elements, element_aspects: {uv: true}}); + scope.dragging_uv = true; + } + } + + function stop() { + removeEventListeners(document, 'mousemove touchmove', drag); + removeEventListeners(document, 'mouseup touchend', stop); + if (scope.dragging_uv) { + UVEditor.disableAutoUV() + Undo.finishEdit('Rotate UV') + setTimeout(() => scope.dragging_uv = false, 10); + } else { + Undo.cancelEdit(); + } + } + addEventListeners(document, 'mousemove touchmove', drag); + addEventListeners(document, 'mouseup touchend', stop); + + }, dragVertices(element, vertex_key, event) { if (event.which == 2 || event.which == 3) return; @@ -2076,10 +2204,18 @@ Interface.definePanels(function() {

-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
@@ -2113,11 +2249,13 @@ Interface.definePanels(function() { diff --git a/package-lock.json b/package-lock.json index 4719d501..c021d993 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "Blockbench", - "version": "4.0.0-beta.0", + "version": "4.0.0-beta.2", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1188,9 +1188,9 @@ "dev": true }, "@electron/get": { - "version": "1.12.4", - "resolved": "https://registry.npmjs.org/@electron/get/-/get-1.12.4.tgz", - "integrity": "sha512-6nr9DbJPUR9Xujw6zD3y+rS95TyItEVM0NVjt1EehY2vUWfIgPiIPVHxCvaTS0xr2B+DRxovYVKbuOWqC35kjg==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@electron/get/-/get-1.13.0.tgz", + "integrity": "sha512-+SjZhRuRo+STTO1Fdhzqnv9D2ZhjxXP6egsJ9kiO8dtP68cDx7dFCwWi64dlMQV7sWcfW1OYCW4wviEBzmRsfQ==", "dev": true, "requires": { "debug": "^4.1.1", @@ -2111,9 +2111,9 @@ } }, "boolean": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.1.2.tgz", - "integrity": "sha512-YN6UmV0FfLlBVvRvNPx3pz5W/mUoYB24J4WSXOKP/OOJpi+Oq6WYqPaNTHzjI0QzwWtnvEd5CGYyQPgp1jFxnw==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.1.4.tgz", + "integrity": "sha512-3hx0kwU3uzG6ReQ3pnaFQPSktpBw6RHN3/ivDKEuU8g1XSfafowyvDnadjv1xp8IZqhtSukxlwv9bF6FhX8m0w==", "dev": true, "optional": true }, @@ -2603,9 +2603,9 @@ } }, "core-js": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.14.0.tgz", - "integrity": "sha512-3s+ed8er9ahK+zJpp9ZtuVcDoFzHNiZsPbNAAE4KXgrRHbjSqqNN6xGSXq6bq7TZIbKj4NLrLb6bJ5i+vSVjHA==", + "version": "3.17.3", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.17.3.tgz", + "integrity": "sha512-lyvajs+wd8N1hXfzob1LdOCCHFU4bGMbqqmLn1Q4QlCpDqWPpGf+p0nj+LNrvDDG33j0hZXw2nsvvVpHysxyNw==", "dev": true, "optional": true }, @@ -2848,9 +2848,9 @@ } }, "electron": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/electron/-/electron-13.1.2.tgz", - "integrity": "sha512-aNT9t+LgdQaZ7FgN36pN7MjSEoj+EWc2T9yuOqBApbmR4HavGRadSz7u9N2Erw2ojdIXtei2RVIAvVm8mbDZ0g==", + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/electron/-/electron-13.3.0.tgz", + "integrity": "sha512-d/BvOLDjI4i7yf9tqCuLL2fFGA2TrM/D9PyRpua+rJolG0qrwp/FohP02L0m+44kmPpofIo4l3NPwLmzyKKimA==", "dev": true, "requires": { "@electron/get": "^1.0.1", @@ -2859,9 +2859,9 @@ }, "dependencies": { "@types/node": { - "version": "14.17.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.3.tgz", - "integrity": "sha512-e6ZowgGJmTuXa3GyaPbTGxX17tnThl2aSSizrFthQ7m9uLGZBXiGhgE55cjRZTF5kjZvYn9EOPOMljdjwbflxw==", + "version": "14.17.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.17.tgz", + "integrity": "sha512-niAjcewgEYvSPCZm3OaM9y6YQrL2SEPH9PymtE6fuZAvFiP6ereCcvApGl2jKTq7copTIguX3PBvfP08LN4LvQ==", "dev": true } } diff --git a/package.json b/package.json index 392e98c7..5926f1ba 100644 --- a/package.json +++ b/package.json @@ -104,7 +104,7 @@ }, "devDependencies": { "blockbench-types": "^3.9.0", - "electron": "^13.1.2", + "electron": "^13.3.0", "electron-builder": "^22.11.11", "electron-notarize": "^1.0.0", "webpack": "^5.21.2",