2024-07-23 22:45:27 -04:00
|
|
|
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
|
|
|
|
|
|
|
|
|
"use strict";
|
|
|
|
|
|
|
|
|
|
// dep: add.array
|
|
|
|
|
// dep: add.three
|
2024-07-24 22:21:40 -04:00
|
|
|
// dep: geo.polygon
|
2024-07-23 22:45:27 -04:00
|
|
|
// dep: moto.license
|
|
|
|
|
// dep: moto.client
|
|
|
|
|
// dep: mesh.object
|
2024-07-24 19:30:44 -04:00
|
|
|
// dep: mesh.api
|
2024-07-23 22:45:27 -04:00
|
|
|
// use: mesh.util
|
|
|
|
|
// use: mesh.group
|
|
|
|
|
gapp.register("mesh.sketch", [], (root, exports) => {
|
|
|
|
|
|
2024-07-24 22:21:40 -04:00
|
|
|
const { BufferGeometry, BufferAttribute } = THREE;
|
2024-07-23 22:45:27 -04:00
|
|
|
const { MeshBasicMaterial, LineBasicMaterial, LineSegments, DoubleSide } = THREE;
|
2024-07-24 15:57:32 -04:00
|
|
|
const { PlaneGeometry, EdgesGeometry, SphereGeometry, Vector3, Mesh, Group } = THREE;
|
2024-07-24 19:59:46 -04:00
|
|
|
const { base, mesh, moto } = root;
|
2024-07-23 22:45:27 -04:00
|
|
|
const { space } = moto;
|
2024-07-24 19:30:44 -04:00
|
|
|
const { api, util } = mesh;
|
2024-07-24 19:59:46 -04:00
|
|
|
const { newPolygon } = base;
|
2024-07-23 22:45:27 -04:00
|
|
|
|
|
|
|
|
const mapp = mesh;
|
|
|
|
|
const worker = moto.client.fn;
|
|
|
|
|
|
|
|
|
|
const material = {
|
2024-07-24 15:57:32 -04:00
|
|
|
normal: new MeshBasicMaterial({ color: 0x888888, side: DoubleSide, transparent: true, opacity: 0.25 }),
|
|
|
|
|
selected: new MeshBasicMaterial({ color: 0x889988, side: DoubleSide, transparent: true, opacity: 0.25 }),
|
2024-07-24 19:59:46 -04:00
|
|
|
highlight: new LineBasicMaterial({ color: 0x88ff88, side: DoubleSide, transparent: true, opacity: 0.50 }),
|
2024-07-23 22:45:27 -04:00
|
|
|
};
|
|
|
|
|
|
2024-07-24 19:30:44 -04:00
|
|
|
function log() {
|
|
|
|
|
mesh.api.log.emit(...arguments);
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-23 22:45:27 -04:00
|
|
|
/** 2D plane containing open and closed polygons which can be extruded **/
|
|
|
|
|
mesh.sketch = class MeshSketch extends mesh.object {
|
2024-07-24 12:29:55 -04:00
|
|
|
constructor(opt = {}) {
|
|
|
|
|
super(opt.id);
|
2024-07-23 22:45:27 -04:00
|
|
|
|
2024-07-24 12:29:55 -04:00
|
|
|
this.file = opt.file || this.id;
|
2024-07-24 15:57:32 -04:00
|
|
|
this.scale = opt.scale || { x: 10, y: 10, z: 0 };
|
2024-07-24 12:29:55 -04:00
|
|
|
this.center = opt.center || { x: 0, y: 0, z: 0 };
|
|
|
|
|
this.normal = opt.normal || { x: 0, y: 0, z: 1 };
|
2024-07-24 19:59:46 -04:00
|
|
|
this.items = opt.items || [];
|
2024-07-24 12:29:55 -04:00
|
|
|
|
2024-07-24 15:57:32 -04:00
|
|
|
const group = this.group = new Group();
|
|
|
|
|
group.sketch = this;
|
|
|
|
|
|
2024-07-24 12:29:55 -04:00
|
|
|
const planeGeometry = new PlaneGeometry(1, 1);
|
2024-07-24 15:57:32 -04:00
|
|
|
const planeMaterial = material.normal;
|
2024-07-23 22:45:27 -04:00
|
|
|
const plane = this.plane = new Mesh(planeGeometry, planeMaterial);
|
2024-07-24 15:57:32 -04:00
|
|
|
plane.sketch = this;
|
2024-07-23 22:45:27 -04:00
|
|
|
|
|
|
|
|
const outlineGeometry = new EdgesGeometry(planeGeometry);
|
2024-07-24 15:57:32 -04:00
|
|
|
const outlineMaterial = material.normal;
|
2024-07-23 22:45:27 -04:00
|
|
|
const outline = this.outline = new LineSegments(outlineGeometry, outlineMaterial);
|
|
|
|
|
|
2024-07-24 15:57:32 -04:00
|
|
|
const handleGeometry = new SphereGeometry(0.5, 16, 16);
|
|
|
|
|
const handleMaterial = material.normal;
|
2024-07-23 22:45:27 -04:00
|
|
|
|
2024-07-24 15:57:32 -04:00
|
|
|
const handles = this.handles = [];
|
|
|
|
|
const corners = this.corners = [ [-0.5, 0.5, 0], [0.5, 0.5, 0], [-0.5, -0.5, 0], [0.5, -0.5, 0] ];
|
2024-07-23 22:45:27 -04:00
|
|
|
|
|
|
|
|
for (let corner of corners) {
|
|
|
|
|
const handle = new Mesh(handleGeometry, handleMaterial);
|
|
|
|
|
handle.position.set(...corner);
|
2024-07-24 15:57:32 -04:00
|
|
|
handle.sketch = this;
|
2024-07-24 19:59:46 -04:00
|
|
|
handles.push(handle);
|
2024-07-23 22:45:27 -04:00
|
|
|
}
|
2024-07-24 12:29:55 -04:00
|
|
|
|
2024-07-24 19:59:46 -04:00
|
|
|
group.add(...handles, outline, plane);
|
2024-07-24 15:57:32 -04:00
|
|
|
|
2024-07-24 22:21:40 -04:00
|
|
|
util.defer(() => this.render());
|
2024-07-24 12:29:55 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
update() {
|
2024-07-24 19:30:44 -04:00
|
|
|
const { group, plane, outline, center, handles, corners, normal, scale } = this;
|
2024-07-24 12:29:55 -04:00
|
|
|
|
|
|
|
|
plane.scale.set(scale.x, scale.y, 1);
|
2024-07-24 15:57:32 -04:00
|
|
|
outline.scale.set(scale.x, scale.y, 1);
|
2024-07-24 19:59:46 -04:00
|
|
|
group.position.set(center.x, center.y, center.z);
|
2024-07-24 15:57:32 -04:00
|
|
|
|
|
|
|
|
for (let i=0; i<4; i++) {
|
|
|
|
|
const handle = handles[i];
|
|
|
|
|
const corner = corners[i];
|
|
|
|
|
handle.position.set(
|
2024-07-24 19:59:46 -04:00
|
|
|
(corner[0] * scale.x),
|
|
|
|
|
(corner[1] * scale.y),
|
|
|
|
|
(corner[2] * scale.z)
|
2024-07-24 15:57:32 -04:00
|
|
|
);
|
|
|
|
|
}
|
2024-07-24 12:29:55 -04:00
|
|
|
|
2024-07-24 19:30:44 -04:00
|
|
|
const { position } = group;
|
|
|
|
|
group.lookAt(new Vector3(
|
|
|
|
|
normal.x + position.x,
|
|
|
|
|
normal.z + position.z,
|
2024-07-24 19:59:46 -04:00
|
|
|
normal.y - position.y
|
2024-07-24 19:30:44 -04:00
|
|
|
));
|
2024-07-24 12:29:55 -04:00
|
|
|
|
|
|
|
|
this.#db_save();
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-24 19:30:44 -04:00
|
|
|
lookat(x,y,z) {
|
|
|
|
|
this.group.lookAt(new Vector3(x,y,z));
|
|
|
|
|
moto.space.update();
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-24 12:29:55 -04:00
|
|
|
#db_save() {
|
2024-07-24 19:59:46 -04:00
|
|
|
const { center, normal, scale, type, file, items } = this;
|
|
|
|
|
mapp.db.space.put(this.id, { center, normal, scale, type, file, items });
|
2024-07-24 12:29:55 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#db_remove() {
|
|
|
|
|
mapp.api.sketch.remove(this);
|
|
|
|
|
mapp.db.space.remove(this.id);
|
2024-07-23 22:45:27 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
get type() {
|
|
|
|
|
return "sketch";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
get object() {
|
2024-07-24 15:57:32 -04:00
|
|
|
return this.group;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
get meshes() {
|
2024-07-25 14:05:44 -04:00
|
|
|
return this.group.children.filter(c => {
|
|
|
|
|
return c.sketch || (c.sketch_item && c.sketch_item.selected) ? c : undefined
|
|
|
|
|
}).reverse();
|
2024-07-23 22:45:27 -04:00
|
|
|
}
|
|
|
|
|
|
2024-07-25 14:48:36 -04:00
|
|
|
get selected_items() {
|
|
|
|
|
return this.items.filter(i => i.selected);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// return true if any selections were deleted;
|
|
|
|
|
delete_selected() {
|
|
|
|
|
let sel = this.items.filter(i => i.selected);
|
|
|
|
|
if (sel.length) {
|
|
|
|
|
this.items = this.items.filter(i => !i.selected);
|
|
|
|
|
this.render();
|
|
|
|
|
return true;
|
|
|
|
|
} else {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-23 22:45:27 -04:00
|
|
|
rename(newname) {
|
|
|
|
|
this.file = newname;
|
2024-07-24 12:29:55 -04:00
|
|
|
this.#db_save();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
remove() {
|
|
|
|
|
this.#db_remove();
|
2024-07-23 22:45:27 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
highlight() {
|
|
|
|
|
this.outline.material = material.highlight;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
unhighlight() {
|
2024-07-24 15:57:32 -04:00
|
|
|
this.outline.material = material.normal;
|
2024-07-23 22:45:27 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
select(bool) {
|
2024-07-24 15:57:32 -04:00
|
|
|
const { plane, handles } = this;
|
2024-07-23 22:45:27 -04:00
|
|
|
if (bool === undefined) {
|
2024-07-24 15:57:32 -04:00
|
|
|
return plane.material === material.selected;
|
2024-07-23 22:45:27 -04:00
|
|
|
}
|
|
|
|
|
if (bool.toggle) {
|
|
|
|
|
return this.select(!this.select());
|
|
|
|
|
}
|
2024-07-24 15:57:32 -04:00
|
|
|
plane.material = (bool ? material.selected : material.normal);
|
|
|
|
|
for (let handle of handles) {
|
|
|
|
|
handle.material = bool ? material.highlight : plane.material;
|
|
|
|
|
}
|
2024-07-25 14:05:44 -04:00
|
|
|
this.render();
|
2024-07-24 15:57:32 -04:00
|
|
|
return bool;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
move(x, y, z = 0) {
|
2024-07-24 19:30:44 -04:00
|
|
|
const { group, center, scale, plane, handles, dragging } = this;
|
2024-07-24 15:57:32 -04:00
|
|
|
const handle = handles.indexOf(dragging);
|
|
|
|
|
if (dragging === plane) {
|
|
|
|
|
center.x += x;
|
|
|
|
|
center.y += y;
|
|
|
|
|
center.z += z;
|
|
|
|
|
this.update();
|
|
|
|
|
} else if (handle >= 0) {
|
|
|
|
|
const sf = [
|
|
|
|
|
[-1, 1, 1],
|
|
|
|
|
[ 1, 1, 1],
|
|
|
|
|
[-1,-1, 1],
|
|
|
|
|
[ 1,-1, 1],
|
|
|
|
|
][handle];
|
|
|
|
|
center.x += x / 2;
|
|
|
|
|
center.y += y / 2;
|
|
|
|
|
center.z += z / 2;
|
|
|
|
|
scale.x += x * sf[0];
|
|
|
|
|
scale.y += y * sf[1];
|
|
|
|
|
scale.z += z * sf[2];
|
|
|
|
|
this.update();
|
2024-07-25 14:05:44 -04:00
|
|
|
} else if (Array.isArray(dragging)) {
|
|
|
|
|
for (let item of dragging) {
|
|
|
|
|
let { center } = item;
|
|
|
|
|
center.x += x;
|
|
|
|
|
center.y += y;
|
|
|
|
|
center.z += z;
|
|
|
|
|
}
|
|
|
|
|
this.render();
|
2024-07-24 15:57:32 -04:00
|
|
|
} else {
|
2024-07-24 19:30:44 -04:00
|
|
|
this.center = {x, y, z};
|
|
|
|
|
this.update();
|
2024-07-24 15:57:32 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
drag(opt = {}) {
|
2024-07-25 14:05:44 -04:00
|
|
|
let { items } = this;
|
2024-07-24 15:57:32 -04:00
|
|
|
if (opt.start) {
|
2024-07-25 14:05:44 -04:00
|
|
|
let selected = items.filter(i => i.selected);
|
|
|
|
|
this.dragging = opt.start.sketch_item ? selected : opt.start;
|
2024-07-24 15:57:32 -04:00
|
|
|
} else if (opt.end) {
|
2024-07-24 19:30:44 -04:00
|
|
|
this.dragging = undefined;
|
2024-07-24 15:57:32 -04:00
|
|
|
} else {
|
|
|
|
|
console.log({ invalid_sketch_drag: opt });
|
|
|
|
|
}
|
2024-07-23 22:45:27 -04:00
|
|
|
}
|
|
|
|
|
|
2024-07-24 19:59:46 -04:00
|
|
|
add_circle(opt = {}) {
|
2024-07-24 19:30:44 -04:00
|
|
|
log(this.file || this.id, '| add circle');
|
2024-07-24 22:21:40 -04:00
|
|
|
Object.assign(opt, { center: {x:0, y:0, z:0}, radius:5 }, opt);
|
2024-07-24 19:59:46 -04:00
|
|
|
this.items.push({ type: "circle", ...opt });
|
2024-07-24 22:21:40 -04:00
|
|
|
this.render();
|
2024-07-24 19:30:44 -04:00
|
|
|
}
|
|
|
|
|
|
2024-07-24 19:59:46 -04:00
|
|
|
add_rectangle(opt = {}) {
|
2024-07-24 19:30:44 -04:00
|
|
|
log(this.file || this.id, '| add rectangle');
|
2024-07-24 22:21:40 -04:00
|
|
|
Object.assign(opt, { center: {x:0, y:0, z:0}, width:15, height:10 }, opt);
|
2024-07-24 19:59:46 -04:00
|
|
|
this.items.push({ type: "rectangle", ...opt });
|
2024-07-24 22:21:40 -04:00
|
|
|
this.render();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// render items unto the group object
|
|
|
|
|
render() {
|
|
|
|
|
let { group } = this;
|
|
|
|
|
// remove previous item/poly-based children of group
|
2024-07-25 14:48:36 -04:00
|
|
|
group.children.filter(c => c.sketch_item || c.sketch_line).forEach(c => group.remove(c));
|
2024-07-24 22:21:40 -04:00
|
|
|
// mapy items into polys into meshes to add to group
|
2024-07-25 14:48:36 -04:00
|
|
|
for (let si of this.items.map((i,o) => new SketchItem(this, i, o))) {
|
2024-07-25 14:05:44 -04:00
|
|
|
group.add(si.mesh);
|
2024-07-25 14:48:36 -04:00
|
|
|
group.add(si.outs);
|
2024-07-25 14:05:44 -04:00
|
|
|
}
|
2024-07-24 19:59:46 -04:00
|
|
|
this.update();
|
2024-07-24 19:30:44 -04:00
|
|
|
}
|
2024-07-25 00:05:24 -04:00
|
|
|
|
|
|
|
|
extrude(opt = {}) {
|
|
|
|
|
let { z } = opt;
|
|
|
|
|
let models = [];
|
2024-07-25 14:05:44 -04:00
|
|
|
for (let item of this.group.children.filter(c => c.sketch_item)) {
|
|
|
|
|
let vert = item.sketch_item.extrude(z || 10);
|
|
|
|
|
let nmdl = new mesh.model({ file: "item", mesh: vert.toFloat32() });
|
2024-07-25 00:05:24 -04:00
|
|
|
models.push(nmdl);
|
|
|
|
|
}
|
|
|
|
|
if (models.length) {
|
|
|
|
|
log('extrude', this.file || this.id, 'into', models.length, 'solid(s)');
|
2024-07-25 14:05:44 -04:00
|
|
|
let { center } = this;
|
2024-07-25 00:05:24 -04:00
|
|
|
let ngrp = api.group.new(models);
|
|
|
|
|
ngrp.floor();
|
2024-07-25 14:48:36 -04:00
|
|
|
ngrp.move(center.x, center.y, center.z);
|
2024-07-25 14:05:44 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class SketchItem {
|
2024-07-25 14:48:36 -04:00
|
|
|
constructor(sketch, item, order) {
|
2024-07-25 14:05:44 -04:00
|
|
|
this.sketch = sketch;
|
|
|
|
|
this.item = item;
|
2024-07-25 14:48:36 -04:00
|
|
|
this.order = order;
|
2024-07-25 14:05:44 -04:00
|
|
|
this.update();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
get type() {
|
|
|
|
|
return "sketch_item";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
get selected() {
|
|
|
|
|
return this.item.selected;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
toggle() {
|
|
|
|
|
this.item.selected = !this.item.selected;
|
|
|
|
|
this.update();
|
|
|
|
|
this.sketch.render();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extrude(opt = {}) {
|
|
|
|
|
return this.poly.extrude(opt);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
update() {
|
2024-07-25 14:48:36 -04:00
|
|
|
let bump = 0.0025;
|
|
|
|
|
let { item, sketch, order } = this;
|
2024-07-25 14:05:44 -04:00
|
|
|
let { material } = mesh;
|
|
|
|
|
let { type, center, width, height, radius, spacing, poly, selected } = item;
|
|
|
|
|
if (type === 'circle') {
|
|
|
|
|
let circumference = 2 * Math.PI * radius;
|
|
|
|
|
let points = Math.floor(circumference / (spacing || 1));
|
|
|
|
|
poly = newPolygon().centerCircle(center, radius, points).annotate({ item } );
|
|
|
|
|
} else if (type === 'rectangle') {
|
|
|
|
|
poly = newPolygon().centerRectangle(center, width, height).annotate({ item });
|
|
|
|
|
} else {
|
|
|
|
|
throw `invalid sketch type: ${type}`;
|
2024-07-25 00:05:24 -04:00
|
|
|
}
|
2024-07-25 14:05:44 -04:00
|
|
|
this.poly = poly;
|
2024-07-25 14:48:36 -04:00
|
|
|
let isSelected = selected && sketch.select();
|
|
|
|
|
// create solid filled area
|
|
|
|
|
let mat = (isSelected ? material.select : material.normal).clone();
|
|
|
|
|
mat.transparent = true;
|
|
|
|
|
mat.opacity = 0.5;
|
2024-07-25 14:05:44 -04:00
|
|
|
let vrt = poly.extrude(0).toFloat32();
|
|
|
|
|
let geo = new BufferGeometry();
|
2024-07-25 14:48:36 -04:00
|
|
|
geo.setAttribute('position', new BufferAttribute(vrt, 3));
|
|
|
|
|
let meh = this.mesh = new Mesh(geo, mat);
|
|
|
|
|
meh.renderOrder = -1;
|
|
|
|
|
meh.sketch_item = this;
|
|
|
|
|
// bump z to avoid z order conflict and ensure item ray intersect priority
|
|
|
|
|
meh.position.z += bump + (order * bump);
|
|
|
|
|
// create poly outline
|
|
|
|
|
let lpt = poly.points.map(p => new Vector3(p.x, p.y, p.z));
|
|
|
|
|
lpt.push(lpt[0]);
|
|
|
|
|
let lge = new BufferGeometry().setFromPoints(lpt);
|
|
|
|
|
let out = this.outs = new THREE.Line(lge, material.wireline);
|
|
|
|
|
out.renderOrder = -1;
|
|
|
|
|
out.sketch_line = this;
|
|
|
|
|
out.position.z += bump + (order * bump);
|
2024-07-25 00:05:24 -04:00
|
|
|
}
|
2024-07-23 22:45:27 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
});
|