add parametric scaling and rotation

This commit is contained in:
Stewart Allen 2022-01-20 15:42:28 -05:00
commit 833e6367e7
4 changed files with 135 additions and 20 deletions

View file

@ -308,11 +308,11 @@ function space_init(data) {
}
},
'keydown', evt => {
let rv = Math.PI / 16;
let { shiftKey, metaKey, ctrlKey, code } = evt;
let rv = (Math.PI / 12);
if (api.modal.showing) {
if (code === 'Escape') {
api.modal.hide();
api.modal.cancel();
}
return;
}

View file

@ -16,6 +16,7 @@ if (mesh.build) return;
let broker = gapp.broker;
let call = broker.send;
let { api, util } = mesh;
let und = undefined;
broker.listeners({
ui_build
@ -33,7 +34,7 @@ util.download = (data, filename = "mesh-data") => {
// add modal dialog functions to api
let modal = api.modal = {
show(title, contents) {
if (this.showing) {
if (this.info.showing) {
throw `modal conflict showing "${title}"`;
}
let bound = h.bind($('modal'), contents);
@ -46,10 +47,14 @@ let modal = api.modal = {
return bound;
},
hide() {
hide(cancel = false) {
if (!modal.info.showing) {
return;
}
clearTimeout(spin_timer);
$('modal_page').style.display = 'none';
modal.info.showing = false;
modal.info.cancelled = cancel;
broker.publish('modal_hide');
if (modal._dialog) {
modal._dialog.resolve(modal._dialog.bound);
@ -57,6 +62,11 @@ let modal = api.modal = {
}
},
cancel() {
// hide but set a flag denoting cancellation
modal.hide(true);
},
info: {
showing: false
},
@ -204,7 +214,7 @@ function ui_build() {
h.div({ id: 'modal_frame' }, [
h.div({ id: 'modal_title'}, [
h.div({ id: 'modal_title_text', _: 'title' }),
h.div({ id: 'modal_title_close', onclick: modal.hide }, [
h.div({ id: 'modal_title_close', onclick: modal.cancel }, [
h.i({ class: "far fa-window-close" })
])
]),
@ -281,20 +291,23 @@ function ui_build() {
]);
// for group/model/box/area/mesh dashboard grids
function grid(v1, v2, side = [ "pos", "rot"], top = [ "X", "Y", "Z" ]) {
function grid(v1, v2, side = [ "pos", "rot"], top = [ "X", "Y", "Z" ], root) {
let v = 'val', l = 'lbl';
let [ X, Y, Z ] = top;
let [ pos, rot ] = side;
return h.div({ class: "grid"}, [
h.div({ _: "" }),
h.div({ _: top[0], class: "top" }),
h.div({ _: top[1], class: "top" }),
h.div({ _: top[2], class: "top" }),
h.div({ _: X, class: "top", id: root ? [ root, l, X ] : und }),
h.div({ _: Y, class: "top", id: root ? [ root, l, Y ] : und }),
h.div({ _: Z, class: "top", id: root ? [ root, l, Z ] : und }),
h.div({ _: side[0], class: "side" }),
h.label({ _: v1[0] }),
h.label({ _: v1[1] }),
h.label({ _: v1[2] }),
h.label({ _: v1[0], id: root ? [ root, v, X, pos ] : und }),
h.label({ _: v1[1], id: root ? [ root, v, Y, pos ] : und }),
h.label({ _: v1[2], id: root ? [ root, v, Z, pos ] : und }),
h.div({ _: side[1], class: "side" }),
h.label({ _: v2[0] }),
h.label({ _: v2[1] }),
h.label({ _: v2[2] }),
h.label({ _: v2[0], id: root ? [ root, v, X, rot ] : und }),
h.label({ _: v2[1], id: root ? [ root, v, Y, rot ] : und }),
h.label({ _: v2[2], id: root ? [ root, v, Z, rot ] : und }),
]);
}
@ -335,6 +348,38 @@ function ui_build() {
h.bind(grouplist, groups);
}
// return function bound to field editing clicks
// builds a modal dialog that updates the object and field
function field_edit(title, get, set) {
return function(ev) {
let onclick = (ev) => {
let tempval = parseFloat($('tempval').value);
api.modal.hide(modal.info.cancelled);
if (modal.info.cancelled) {
return;
}
for (let g of api.selection.groups()) {
let old = get(g);
set(g, tempval, old);
g.floor(mesh.group);
defer_selection(); // update ui
}
};
let onkeydown = (ev) => {
if (ev.key === 'Enter') {
estop(ev);
onclick();
}
};
let { tempval } = api.modal.show(title, h.div({ class: "tempedit" }, [
h.input({ id: 'tempval', value: ev.target.innerText, size: 10, onkeydown }),
h.button({ _: 'set', onclick })
]));
tempval.setSelectionRange(0, tempval.value.length);
tempval.focus();
}
}
// update model information dashboard (bottom)
function update_selection() {
let map = { fixed: 2 };
@ -363,12 +408,19 @@ function ui_build() {
let g_pos = util.average(s_grp.map(g => g.object.position));
let g_rot = util.average(s_grp.map(g => g.object.rotation));
let g_id = s_grp.map(g => g.id).join(' ');
let h_grp = sblock('group', g_id, grid( util.extract(g_pos, map), util.extract(g_rot, map)) );
let h_grp = sblock('group', g_id, grid(
util.extract(g_pos, map),
util.extract(g_rot, map),
und, und, 'group'
));
let m_pos = util.average(s_mdl.map(m => m.object.position));
let m_rot = util.average(s_mdl.map(m => m.object.rotation));
let m_id = s_mdl.map(m => m.id).join(' ');
let h_mdl = sblock('model', m_id, grid( util.extract(m_pos, map), util.extract(m_rot, map)) );
let h_mdl = sblock('model', m_id, grid(
util.extract(m_pos, map),
util.extract(m_rot, map)
));
let bounds = util.bounds(s_mdl);
let h_bnd = sblock('box', m_id, grid(
@ -379,7 +431,7 @@ function ui_build() {
let h_ara = sblock('span', m_id, grid(
util.extract(bounds.center, map),
util.extract(bounds.size, map),
[ "center", "size" ]
[ "center", "size" ], und, 'span'
));
let t_vert = s_mdl.map(m => m.vertices).reduce((a,v) => a+v);
@ -403,6 +455,52 @@ function ui_build() {
...h_ara,
...h_msh
]);
// bind rotation editable fields
let { group_val_X_rot, group_val_Y_rot, group_val_Z_rot } = bound;
[ group_val_X_rot, group_val_Y_rot, group_val_Z_rot ].forEach(e => {
e.classList.add('editable');
});
group_val_X_rot.onclick = field_edit('x rotation', group => {
return group.rotation();
}, (group, val, old) => {
group.rotation(val, old.y, old.z);
});
group_val_Y_rot.onclick = field_edit('y rotation', group => {
return group.rotation();
}, (group, val, old) => {
group.rotation(old.x, val, old.z);
});
group_val_Z_rot.onclick = field_edit('z rotation', group => {
return group.rotation();
}, (group, val, old) => {
group.rotation(old.x, old.y, val);
});
// bind scale editable fields
let { span_val_X_size, span_val_Y_size, span_val_Z_size } = bound;
[ span_val_X_size, span_val_Y_size, span_val_Z_size ].forEach(e => {
e.classList.add('editable');
});
span_val_X_size.onclick = field_edit('x size', group => {
return group.scale();
}, (group, val, old) => {
val = val / group.bounds.dim.x;
group.scale(val, old.y, old.z);
});
span_val_Y_size.onclick = field_edit('y size', group => {
return group.scale();
}, (group, val, old) => {
val = val / group.bounds.dim.y;
group.scale(old.x, val, old.z);
});
span_val_Z_size.onclick = field_edit('z size', group => {
return group.scale();
}, (group, val, old) => {
val = val / group.bounds.dim.z;
group.scale(old.x, old.y, val);
});
let pmap = prefs.map.info;
// map buttons to show/hide selection info
function toggle(label, h, s, d) {

View file

@ -57,7 +57,7 @@ function build(data, context) {
if (key === '_') {
text = val;
} else if (key === 'id') {
elid = val;
elid = val ? (tov === 'object' ? val.join('_') : val) : undefined;
} else if (tov === 'function') {
func[key] = val;
elid = elid || `_${nextid++}`;

View file

@ -250,8 +250,9 @@ button:hover {
#modal_title_close {
position: absolute;
right: 5px;
color: #555;
right: 2px;
top: 2px;
}
#modal_title_close:hover {
@ -503,3 +504,19 @@ button.selected:hover {
#selectlist .grid2 {
grid: min-content 1fr / min-content 1fr;
}
.tempedit {
gap: 3px;
}
.tempedit input {
text-align: center;
}
.editable {
background-color: rgba(0,128,255,0.2) !important;
}
.editable:hover {
background-color: rgba(0,128,255,0.5) !important;
}