diff --git a/src/kiri/app/init/input.js b/src/kiri/app/init/input.js index 55d0c46a..6b4c881a 100644 --- a/src/kiri/app/init/input.js +++ b/src/kiri/app/init/input.js @@ -177,7 +177,7 @@ export function init_input() { space.sky.showGrid(false); space.sky.setColor(controller.dark ? 0 : 0xffffff); space.setAntiAlias(controller.antiAlias); - space.init(container, function (delta) { + space.init(container, (delta) => { const { lo, hi, max } = slider.getRange(); if (max === 0 || !delta) return; if (controller.reverseZoom) delta = -delta; @@ -471,7 +471,7 @@ export function init_input() { DOC.body.requestFullscreen(); } catch (e) { event.emit('resize'); - moto.space.event.onResize(); + space.event.onResize(); } }; diff --git a/src/kiri/app/mouse.js b/src/kiri/app/mouse.js index d4807d6c..836f19c9 100644 --- a/src/kiri/app/mouse.js +++ b/src/kiri/app/mouse.js @@ -113,10 +113,7 @@ class InteractionControl { this.#space.mouse.downSelect((int, event) => { // Feature hook for custom mouse down handling if (this.#api.feature.on_mouse_down) { - if (int) { - this.#api.feature.on_mouse_down(int, event); - return; - } + return this.#api.feature.on_mouse_down(int, event); } // Hover mode handling @@ -178,9 +175,10 @@ class InteractionControl { }); // Drag handler - this.#space.mouse.onDrag((delta, offset, up = false) => { + this.#space.mouse.onDrag((delta, offset, up = false, int) => { if (this.#api.feature.hover) { - return; + return this.#api.feature?.on_mouse_drag({ delta, offset, up, int }); + // return; } if (up) { diff --git a/src/kiri/app/paint.js b/src/kiri/app/paint.js index 93abbe33..367fffe2 100644 --- a/src/kiri/app/paint.js +++ b/src/kiri/app/paint.js @@ -71,21 +71,45 @@ import { THREE } from '../../ext/three.js'; * @returns {THREE.Material} The modified material */ function addPaintOverlaySimple(material, paintPoints, paintColor = new THREE.Color(0x4488ff)) { + // Initialize userData if it doesn't exist + if (!material.userData) { + material.userData = {}; + } + + // Store initial paint points reference + material.userData.paintPoints = paintPoints; + material.userData.paintColor = paintColor; + material.onBeforeCompile = (shader) => { // Store shader reference for later updates material.userData.shader = shader; + // Use updated paint points if they were modified before first compile + const currentPoints = material.userData.paintPoints || paintPoints; + // Convert paint points to vec4 array (x, y, z, radius) - const points = paintPoints.map(p => - new THREE.Vector4(p.point.x, p.point.y, p.point.z, p.radius) - ); + // Filter out invalid entries + const validPoints = currentPoints + .filter(p => p && p.point && typeof p.radius === 'number') + .map(p => new THREE.Vector4(p.point.x, p.point.y, p.point.z, p.radius)); + + // Pad array to exactly 256 elements (shader uniform array size) + // THREE.js requires the array length to match the shader declaration + const points = new Array(256); + for (let i = 0; i < 256; i++) { + points[i] = i < validPoints.length ? validPoints[i] : new THREE.Vector4(0, 0, 0, 0); + } + + console.log('[Paint Shader] onBeforeCompile called with', validPoints.length, 'valid points'); + console.log('[Paint Shader] First point:', currentPoints[0]); // Add uniforms for paint data shader.uniforms.paintPoints = { value: points }; - shader.uniforms.paintCount = { value: points.length }; + shader.uniforms.paintCount = { value: validPoints.length }; shader.uniforms.paintColor = { value: paintColor }; - // Pass world position from vertex shader to fragment shader + // Pass model position from vertex shader to fragment shader + // Paint points are in widget/model space, so we compare against transformed (model space) shader.vertexShader = shader.vertexShader.replace( `#include `, ` @@ -128,9 +152,14 @@ function addPaintOverlaySimple(material, paintPoints, paintColor = new THREE.Col } if (painted) { - // Blend paint color with existing fragment color - gl_FragColor.rgb = mix(gl_FragColor.rgb, paintColor, 0.5); + // Blend paint color with existing fragment color (increased to 0.7 for visibility) + gl_FragColor.rgb = mix(gl_FragColor.rgb, paintColor, 0.7); } + + // DEBUG: Uncomment to tint entire mesh when shader is active (proves shader is running) + // if (paintCount > 0) { + // gl_FragColor.rgb += vec3(0.3, 0.0, 0.0); // Add red tint to entire mesh + // } ` ); }; @@ -154,21 +183,36 @@ function addPaintOverlaySimple(material, paintPoints, paintColor = new THREE.Col * @returns {THREE.Material} The modified material */ function addPaintOverlayTexture(material, paintPoints, paintColor = new THREE.Color(0x4488ff)) { + // Initialize userData if it doesn't exist + if (!material.userData) { + material.userData = {}; + } + + // Store initial paint points reference + material.userData.paintPoints = paintPoints; + material.userData.paintColor = paintColor; + material.onBeforeCompile = (shader) => { // Store shader reference for later updates material.userData.shader = shader; + // Use updated paint points if they were modified before first compile + const currentPoints = material.userData.paintPoints || paintPoints; + + // Filter out invalid entries + const validPoints = currentPoints.filter(p => p && p.point && typeof p.radius === 'number'); + // Create data texture from paint points // Texture is laid out as square grid: width x height >= point count - const pointCount = paintPoints.length; + const pointCount = validPoints.length; const texWidth = Math.ceil(Math.sqrt(pointCount)); const texHeight = Math.ceil(pointCount / texWidth); const data = new Float32Array(texWidth * texHeight * 4); // Pack paint points into texture data // Each point = 4 floats (x, y, z, radius) - for (let i = 0; i < paintPoints.length; i++) { - const p = paintPoints[i]; + for (let i = 0; i < validPoints.length; i++) { + const p = validPoints[i]; data[i * 4 + 0] = p.point.x; data[i * 4 + 1] = p.point.y; data[i * 4 + 2] = p.point.z; @@ -191,7 +235,8 @@ function addPaintOverlayTexture(material, paintPoints, paintColor = new THREE.Co shader.uniforms.paintTexSize = { value: new THREE.Vector2(texWidth, texHeight) }; shader.uniforms.paintColor = { value: paintColor }; - // Pass world position from vertex shader to fragment shader + // Pass model position from vertex shader to fragment shader + // Paint points are in widget/model space, so we compare against transformed (model space) shader.vertexShader = shader.vertexShader.replace( `#include `, ` @@ -245,9 +290,14 @@ function addPaintOverlayTexture(material, paintPoints, paintColor = new THREE.Co } if (painted) { - // Blend paint color with existing fragment color - gl_FragColor.rgb = mix(gl_FragColor.rgb, paintColor, 0.5); + // Blend paint color with existing fragment color (increased to 0.7 for visibility) + gl_FragColor.rgb = mix(gl_FragColor.rgb, paintColor, 0.7); } + + // DEBUG: Uncomment to tint entire mesh when shader is active (proves shader is running) + // if (paintCount > 0) { + // gl_FragColor.rgb += vec3(0.3, 0.0, 0.0); // Add red tint to entire mesh + // } ` ); }; @@ -282,16 +332,32 @@ function addPaintOverlayAuto(material, paintPoints, paintColor = new THREE.Color * IMPORTANT: The material must have already been initialized with one of the * addPaintOverlay functions before calling this update function. * + * NOTE: If the shader hasn't been compiled yet (first render not complete), + * this will trigger a material update to force recompilation with new points. + * * @param {THREE.Material} material - Material with paint shader already applied * @param {Array<{point: {x,y,z}, radius: number}>} paintPoints - Updated array of paint spheres */ function updatePaintOverlay(material, paintPoints) { + // Initialize userData if it doesn't exist + if (!material.userData) { + material.userData = {}; + } + + // Store paint points reference in userData for access during shader compilation + if (!material.userData.paintPoints) { + material.userData.paintPoints = paintPoints; + } + // Access the compiled shader uniforms // After onBeforeCompile runs once, userData.shader contains the compiled program const shader = material.userData?.shader; if (!shader || !shader.uniforms) { - console.warn('updatePaintOverlay: material does not have compiled paint shader'); + // Shader hasn't compiled yet (first render not complete) + // Store the new points and trigger recompile + material.userData.paintPoints = paintPoints; + material.needsUpdate = true; return; } @@ -315,13 +381,20 @@ function updatePaintOverlaySimple(shader, paintPoints) { const uniforms = shader.uniforms; // Convert paint points to vec4 array (x, y, z, radius) - const points = paintPoints.map(p => - new THREE.Vector4(p.point.x, p.point.y, p.point.z, p.radius) - ); + // Filter out invalid entries + const validPoints = paintPoints + .filter(p => p && p.point && typeof p.radius === 'number') + .map(p => new THREE.Vector4(p.point.x, p.point.y, p.point.z, p.radius)); + + // Pad array to exactly 256 elements (shader uniform array size) + const points = new Array(256); + for (let i = 0; i < 256; i++) { + points[i] = i < validPoints.length ? validPoints[i] : new THREE.Vector4(0, 0, 0, 0); + } // Update uniform values uniforms.paintPoints.value = points; - uniforms.paintCount.value = points.length; + uniforms.paintCount.value = validPoints.length; // Note: No need to set needsUpdate on the material // Uniform updates are automatically detected by THREE.js @@ -334,15 +407,18 @@ function updatePaintOverlaySimple(shader, paintPoints) { function updatePaintOverlayTexture(shader, paintPoints) { const uniforms = shader.uniforms; + // Filter out invalid entries + const validPoints = paintPoints.filter(p => p && p.point && typeof p.radius === 'number'); + // Recreate texture with new paint points - const pointCount = paintPoints.length; + const pointCount = validPoints.length; const texWidth = Math.ceil(Math.sqrt(pointCount)); const texHeight = Math.ceil(pointCount / texWidth); const data = new Float32Array(texWidth * texHeight * 4); // Pack paint points into texture data - for (let i = 0; i < paintPoints.length; i++) { - const p = paintPoints[i]; + for (let i = 0; i < validPoints.length; i++) { + const p = validPoints[i]; data[i * 4 + 0] = p.point.x; data[i * 4 + 1] = p.point.y; data[i * 4 + 2] = p.point.z; diff --git a/src/kiri/app/widget.js b/src/kiri/app/widget.js index b27309fc..9aae365f 100644 --- a/src/kiri/app/widget.js +++ b/src/kiri/app/widget.js @@ -49,11 +49,11 @@ class Widget extends WidgetCore { } const widget = this; index().put('ws-save-'+this.id, { + anno: this.annotations(), geo: widget.getGeoVertices({ unroll: false }).slice(), - track: widget.track, group: this.group.id, meta: this.meta, - anno: this.annotations() + track: widget.track }, result => { widget.meta.saved = Date.now(); if (ondone) ondone(); diff --git a/src/kiri/app/workers.js b/src/kiri/app/workers.js index d8b2049a..808a654b 100644 --- a/src/kiri/app/workers.js +++ b/src/kiri/app/workers.js @@ -19,7 +19,7 @@ let debug = self.debug === true, poolpath; /** - * @param {Function} fn name of function in kiri.worker + * @param {Function} fn name of function in worker * @param {Object} data to send to server * @param {Function} onreply function to call on reply messages * @param {Object[]} zerocopy array of objects to pass using zerocopy @@ -31,14 +31,17 @@ function send(fn, data, onreply, zerocopy) { // track it only when we expect and can handle a reply running[seq] = { fn:onreply }; } + let msg = { seq: seq, task: fn, time: time(), data: data }; + // console.log('client send', msg); // if (!fn) { console.trace({empty_fn: data}) }; + try { worker.postMessage(msg, zerocopy); } catch (error) { @@ -46,10 +49,19 @@ function send(fn, data, onreply, zerocopy) { } } +// Promise wrapper around send() +function sendp(fn, data, zerocopy) { + return new Promise(resolve => { + send(fn, data, resolve, zerocopy); + }); +} + // code is running in the browser / client context export const client = { send, + sendp, + setWorkPath(path) { workpath = path; return client; @@ -269,7 +281,7 @@ export const client = { ondone(null, reply.error); } if (reply.debug) { - api.event.emit("export.debug", reply.debug); + console.log('worker debug', reply.debug); } }); }, diff --git a/src/kiri/app/workspace.js b/src/kiri/app/workspace.js index 92dd3d4b..faae03d9 100644 --- a/src/kiri/app/workspace.js +++ b/src/kiri/app/workspace.js @@ -115,7 +115,7 @@ function save(quiet) { api.conf.save(); const newWidgets = []; const oldWidgets = js2o(SDB.getItem('ws-widgets'), []); - api.widgets.each(function(widget) { + api.widgets.each(widget => { if (widget.synth) return; newWidgets.push(widget.id); oldWidgets.remove(widget.id); diff --git a/src/kiri/core/widget.js b/src/kiri/core/widget.js index 57f5e100..d7791888 100644 --- a/src/kiri/core/widget.js +++ b/src/kiri/core/widget.js @@ -23,6 +23,7 @@ class Widget { this.id = id || Date.now().toString(36)+(nextId++); this.grouped = group ? true : false; + // persisted this.group = group || []; this.group.push(this); if (!this.group.id) { @@ -33,10 +34,8 @@ class Widget { } // rotation stack (for undo) this.roto = []; - // added meshes (supports, tabs, etc) + // overlay meshes (supports, tabs, etc) this.adds = []; - // persisted client annotations (cam tabs, fdm supports) - this.anno = {}; // THREE Mesh and points this.mesh = null; this.points = null; @@ -48,6 +47,24 @@ class Widget { this.settings = null; // used?? this.modified = true; this.boundingBoxNeedsUpdate = true + // cache shadow geo + this.cache = {}; + this.stats = { + slice_time: 0, + load_time: 0, + progress: 0 + }; + // if this is a synthesized support widget + this.support = false; + // persisted: client annotations (cam tabs, fdm supports) + this.anno = {}; + // persisted: file meta-data + this.meta = { + url: null, + file: null, + saved: false + }; + // persisted: location state this.track = { // box size for packer box: { @@ -74,21 +91,7 @@ class Widget { mirror: false, indexed: false, indexRad: 0 - }, - // used to cache shadow geo - this.cache = {}; - this.stats = { - slice_time: 0, - load_time: 0, - progress: 0 }; - this.meta = { - url: null, - file: null, - saved: false - }; - // if this is a synthesized support widget - this.support = false; } annotations() { diff --git a/src/kiri/mode/fdm/driver-be.js b/src/kiri/mode/fdm/driver-be.js index 3abdb04f..8f308c12 100644 --- a/src/kiri/mode/fdm/driver-be.js +++ b/src/kiri/mode/fdm/driver-be.js @@ -1,7 +1,7 @@ /** Copyright Stewart Allen -- All Rights Reserved */ import { util } from '../../../geo/base.js'; -import { fdm_slice, slicePost, supports } from './slice.js'; +import { fdm_slice, slicePost } from './slice.js'; import { fdm_prepare } from './prepare.js'; import { fdm_export } from './export.js'; import { codec } from '../../core/codec.js'; @@ -24,15 +24,15 @@ function extrudeMM(dist, perMM, factor) { // defer loading until client and worker exist function init(worker) { - worker.dispatch.fdm_support_generate = function(data, send) { - const { settings } = data; - const widgets = Object.values(worker.cache); - const fresh = widgets.filter(widget => supports(settings, widget)); - send.done(codec.encode(fresh.map(widget => { return { - id: widget.id, - supports: widget.supports, - } } ))); - }; + // worker.dispatch.fdm_support_generate = function(data, send) { + // const { settings } = data; + // const widgets = Object.values(worker.cache); + // const fresh = widgets.filter(widget => supports(settings, widget)); + // send.done(codec.encode(fresh.map(widget => { return { + // id: widget.id, + // supports: widget.supports, + // } } ))); + // }; } diff --git a/src/kiri/mode/fdm/init-menu.js b/src/kiri/mode/fdm/init-menu.js index c81bfdd5..9dc84e2f 100644 --- a/src/kiri/mode/fdm/init-menu.js +++ b/src/kiri/mode/fdm/init-menu.js @@ -41,6 +41,10 @@ function optSelected(sel) { return opt ? opt.value : undefined; } +function manualSupport() { + return api.conf.proc().sliceSupportType === 'manual'; +} + function hasInfill() { return optSelected(ui.sliceFillType) !== 'none' } @@ -111,7 +115,7 @@ export function menu() { outputFanSpeed: newInput(LANG.ou_fans_s, {title:LANG.ou_fans_l, convert:toInt, bound:bound(0,255)}), outputMinLayerTime: newInput(LANG.ou_layt_s, { title:LANG.ou_layt_l, convert:toInt, bound:bound(0,200) }), _____: newGroup(LANG.sp_menu, $('fdm-support'), { modes:FDM, driven, hideable, separator, group:"fdm-supp" }), - sliceSupportType: newSelect(LANG.sp_type_s, {title:LANG.sp_type_l}, "support"), + sliceSupportType: newSelect(LANG.sp_type_s, {title:LANG.sp_type_l, trigger}, "support"), sliceSupportNozzle: newSelect(LANG.sp_nozl_s, {title:LANG.sp_nozl_l, show:isMultiHead}, "extruders"), separator: newBlank({ class:"set-sep", driven }), sliceSupportAngle: newInput(LANG.sp_angl_s, {title:LANG.sp_angl_l, convert:toFloat, bound:bound(0.0,90.0)}), @@ -127,16 +131,16 @@ export function menu() { sliceSupportTree: newBoolean(LANG.sp_tree_s, onBooleanClick, {title:LANG.sp_tree_s, trigger, show:isNotBelt}), // sliceSupportEnable: newBoolean(LANG.sp_auto_s, onBooleanClick, {title:LANG.sp_auto_l, show:isNotBelt}), sliceSupportOutline: newBoolean(LANG.sp_outl_s, onBooleanClick, {title:LANG.sp_outl_l, show: () => !isTree() }), - separator: newBlank({ class:"set-sep", driven }), + separator: newBlank({ class:"set-sep", driven, show:manualSupport }), sliceSupportGen: newRow([ ui.ssaGen = newButton(LANG.sp_detect, onButtonClick, {class: "f-col grow a-center"}) - ], { modes: FDM, class: "ext-buttons f-row grow", show: () => !ui.sliceSupportTree.checked }), - separator: newBlank({ class:"set-sep", driven }), + ], { modes: FDM, class: "ext-buttons f-row grow", show:manualSupport }), + // separator: newBlank({ class:"set-sep", driven }), sliceSupportManual: newRow([ (ui.ssmAdd = newButton(undefined, onButtonClick, {icon:''})), (ui.ssmDun = newButton(undefined, onButtonClick, {icon:''})), (ui.ssmClr = newButton(undefined, onButtonClick, {icon:''})) - ], {class:"ext-buttons f-row"}), + ], {class:"ext-buttons f-row", show:manualSupport}), _____: newGroup(LANG.fl_menu, $('fdm-base'), { modes:FDM, driven, hideable, separator, group:"fdm-base" }), firstSliceHeight: newInput(LANG.fl_lahi_s, {title:LANG.fl_lahi_l, convert:toFloat, show:isNotBelt}), firstLayerNozzleTemp:newInput(LANG.fl_nozl_s, {title:LANG.fl_nozl_l, convert:toInt, show:isNotBelt}), diff --git a/src/kiri/mode/fdm/init-ui.js b/src/kiri/mode/fdm/init-ui.js index f7e4d0ff..60accf7b 100644 --- a/src/kiri/mode/fdm/init-ui.js +++ b/src/kiri/mode/fdm/init-ui.js @@ -3,24 +3,16 @@ import { $ } from '../../../moto/webui.js'; import { api } from '../../app/api.js'; import { conf } from '../../app/conf/defaults.js'; -import { util } from '../../../geo/base.js'; -import { client } from '../../app/workers.js'; -import { addbox, delbox, getlastbox } from '../../core/boxes.js'; -import { Layers } from '../../app/layers.js'; -import { Stack } from '../../app/stack.js'; -import { Widget, newWidget } from '../../app/widget.js'; -import { space } from '../../../moto/space.js'; +import { addPaintOverlayTexture, updatePaintOverlay } from '../../app/paint.js'; const { Matrix4, Vector3, BufferAttribute, BufferGeometryUtils, Raycaster, Euler } = THREE; const { VIEWS } = api.const; const LANG = api.language.current; -let lastBox, lastMode, lastView, lastPillar, fromPillar, +let lastView, addingSupports = false, isFdmMode = false, - nextID = Date.now(), - p1, p2, iw, alert = [], func = {}; @@ -80,16 +72,6 @@ export function init() { } } - function filterSynth() { - api.widgets.filter((widget) => { - if (widget.track.synth) { - space.world.remove(widget.mesh); - Widget.Groups.remove(widget); - } - return !widget.track.synth - }); - } - function clearRanges() { api.conf.get().process.ranges = []; ui.fdmRanges.innerHTML = ''; @@ -140,19 +122,15 @@ export function init() { api.event.on("mode.set", mode => { isFdmMode = mode === 'FDM'; - lastMode = mode; // hide/show fdm mode elements for (let el of [...document.getElementsByClassName('mode-fdm')]) { api.uc.setClass(el, 'hide', !isFdmMode); } - updateSupportVisiblity(); }); api.event.on("view.set", view => { $('top-slider').style.display = 'none'; lastView = view; - updateSupportVisiblity(); - filterSynth(); func.sdone(); // let ranges = api.conf.get().process.ranges; if (isFdmMode) { @@ -174,7 +152,6 @@ export function init() { api.event.on("settings.load", (settings) => { if (settings.mode !== 'FDM') return; - restoreSupports(api.widgets.all()); updateRanges(settings.process.ranges); }); @@ -191,96 +168,89 @@ export function init() { } }); - api.event.on("fdm.supports.detect", func.sgen = () => { + // auto-detect supports + func.sgen = () => { let alerts = []; - let { process, device } = api.conf.get(); - if (!device.bedBelt) { - if (process.sliceSupportAngle < 10) { - alerts.push(api.show.alert("angles below 10 degrees may fail")); - } - } alerts.push(api.show.alert("analyzing part(s)...", 1000)); - support_generate(array => { - func.sclear(); - api.hide.alert(undefined,alerts); - for (let rec of array) { - let { widget, supports } = rec; - let wa = api.widgets.annotate(widget.id); - let ws = wa.support || []; - for (let support of supports) { - let { from, to, mid } = support; - let dw = api.conf.get().process.sliceSupportSize / 2; - let dh = from.z - to.z; - let rec = { - x: mid.x, - y: mid.y, - z: mid.z, - dw, - dh, - id: Math.random() * 0xffffffffff - }; - addWidgetSupport(widget, rec); - ws.push(Object.clone(rec)); - } - wa.support = ws; - } - api.event.emit("fdm.supports.detected"); - }); - }); + setTimeout(() => api.hide.alert(undefined,alerts), 2000); + } - api.event.on("fdm.supports.add", func.sadd = () => { - alert = api.show.alert("[esc] key cancels support editing"); - api.feature.hover = addingSupports = true; - }); - - api.event.on("fdm.supports.done", func.sdone = () => { - delbox('intZ'); - delbox('intW'); - delbox('supp'); - api.hide.alert(alert); - api.feature.hover = addingSupports = false; - fromPillar = undefined; - }); - - api.event.on("fdm.supports.clear", func.sclear = () => { - func.sdone(); - if (clearAllWidgetSupports()) { - api.conf.save(); + // manual add supports + func.sadd = () => { + if (addingSupports) { + return func.sdone(); } - }); + alert = api.show.alert("[esc] key cancels support editing", 1000); + let down; + api.feature.hover = addingSupports = true; + api.feature.on_mouse_down = (int) => { + if (int) { + let { point } = int; + down = { point, widget: int.object.widget }; + } + return api.widgets.meshes(); + }; + api.feature.on_mouse_drag = ({ int }) => { + if (int?.length) { + func.sadd_point(int[0].point, down.widget); + } + return [ down.widget.mesh ]; + // return api.widgets.meshes(); + }; + api.widgets.each(w => { + let mat = w.cache.mat = w.mesh.material[0]; + mat = w.mesh.material[0] = mat.clone(); + mat.needsUpdate = true; + if (!w.anno.paint) { + w.anno.paint = []; + } + addPaintOverlayTexture(mat, w.anno.paint, new THREE.Color(0x4488ff)); + }) + api.space.update(); + } + + func.sadd_point = (point, widget) => { + let { x, y, z } = point; + let rec = { + point: { x, y:-z, z:y }, + radius: 2 + }; + // return console.log({ on, widget, point }); + let paint = widget.anno.paint; + paint.push(rec); + updatePaintOverlay(widget.mesh.material[0], paint); + api.space.update(); + // api.conf.save(); + + }; + + // manual add done + func.sdone = () => { + if (!addingSupports) { + return; + } + api.feature.hover = addingSupports = false; + api.feature.on_mouse_down = undefined; + api.feature.on_mouse_drag = undefined; + api.hide.alert(alert); + api.widgets.each(w => { + w.mesh.material[0] = w.cache.mat; + delete w.cache.mat; + }); + } + + // manual supports clear + func.sclear = () => { + func.sdone(); + api.widgets.each(w => clearWidgetSupports(w)); + api.conf.save(); + } api.event.on("slice.begin", () => { if (!isFdmMode) { return; } func.sdone(); - updateSupportVisiblity(); - // synth support widget for each widget group - // iow, create support meshes based on support widget annotation - for (let group of Widget.Groups.list()) { - let merge = group.filter(w => w.sups).map(w => Object.values(w.sups)).flat(); - if (!merge.length) { - continue; - } - let boxen = merge.map(m => { - let geo = m.box.geometry.clone(); - if (geo.index) geo = geo.toNonIndexed(); - return geo.translate(m.x, m.y, m.z); - }); - for (let box of boxen) { - // eliminate / normalize uv to allow other widget merge - box.setAttribute('uv',new BufferAttribute(new Float32Array(0), 3)); - } - let bbg = BufferGeometryUtils.mergeGeometries(boxen); - let sw = newWidget(null, group); - let fwp = group[0].track.pos; - sw.loadGeometry(bbg); - sw._move(fwp.x, fwp.y, fwp.z); - api.widgets.add(sw); - sw.track.synth = true; - sw.track.support = true; - space.world.add(sw.mesh); - } }); api.event.on("slice.end", () => { @@ -312,25 +282,12 @@ export function init() { if (!isFdmMode) { return; } - let ann = api.widgets.annotate(widget.id); - if (ann.support) { - for (let supp of Object.values(ann.support)) { - supp.id = Math.abs(Math.random() * 0xffffffffffff); - } - restoreSupports([widget]); - } }); api.event.on("widget.mirror", widget => { if (!isFdmMode) { return; } - let ann = api.widgets.annotate(widget.id); - let sups = ann.support || []; - sups.forEach(sup => { - let wsup = widget.sups[sup.id]; - wsup.box.position.x = wsup.x = sup.x = -sup.x; - }); }); api.event.on("widget.rotate", rot => { @@ -341,305 +298,30 @@ export function init() { if (x || y) { clearWidgetSupports(widget); } else { - let ann = api.widgets.annotate(widget.id); - let sups = ann.support || []; - sups.forEach(sup => { - let wsup = widget.sups[sup.id]; - let vc = new Vector3(sup.x, sup.y, sup.z); - let m4 = new Matrix4(); - m4 = m4.makeRotationFromEuler(new Euler(x || 0, y || 0, z || 0)); - vc.applyMatrix4(m4); - wsup.box.position.x = wsup.x = sup.x = vc.x; - wsup.box.position.y = wsup.y = sup.y = vc.y; - wsup.box.position.z = wsup.z = sup.z = vc.z; - }); + // todo rotate supports when it's only a Z rotation } }); api.event.on("mouse.hover.up", func.hoverup = (on = {}) => { - let { event } = on; - if (!isFdmMode) { + if (!(addingSupports && isFdmMode)) { return; } - if (!addingSupports) { - return; + let { object } = on; + if (object) { + let { point } = object; + let { widget } = object.object; + func.sadd_point(point, widget); } - delbox('supp'); - if (lastPillar) { - removeWidgetSupport(lastPillar.widget, lastPillar); - return; - } - lastBox = getlastbox(); - if (!(iw && lastBox)) return; - let { point, dim } = lastBox; - p1.y = Math.max(0, p1.y); - p2.y = Math.max(0, p2.y); - let rz = dim.rz; - let dh = dim.z; - let dw = api.conf.get().process.sliceSupportSize / 2; - let ip = iw.track.pos; - let wa = api.widgets.annotate(iw.id); - let ws = (wa.support = wa.support || []); - let id = ++nextID; - let rec = {x: point.x - ip.x, y: -point.z - ip.y, z: point.y, rz, dw, dh, id}; - if (fromPillar && event && event.shiftKey) { - let targets = api.widgets.meshes().append(space.internals().platform); - let from = fromPillar, - d = { - x: rec.x - from.x, - y: rec.y - from.y, - z: rec.z - from.z - }; - let dist = Math.sqrt(d.x*d.x + d.y*d.y + d.z*d.z); - let lerp = util.lerp(0,dist,dw).map(v => v/dist); - let angle = rec.rz = Math.atan2(d.y,d.x); - let fromrec = ws.filter(r => r.id === from.id)[0]; - if (fromrec) { - from.rz = fromrec.rz = angle; - removeWidgetSupport(iw, fromPillar); - ws.push(Object.clone(fromrec)); - addWidgetSupport(iw, fromrec); - } - lerp.pop(); - let seed = Math.random(); - for (let pct of lerp) { - let point = new Vector3(from.x + d.x * pct, from.z + d.z * pct, from.y + d.y * pct); - addSupportAtPoint(targets, point, ip, angle, seed++); - } - } - ws.push(Object.clone(rec)); - fromPillar = addWidgetSupport(iw, rec); - api.conf.save(); }); - function addSupportAtPoint(targets, point, ip, angle, id) { - let up = new Vector3(0,1,0) - let dn = new Vector3(0,-1,0) - let rp = new Vector3(point.x + ip.x, point.y, -point.z - ip.y); - let iup = new Raycaster(rp, up) - .intersectObjects(targets, false) - .filter(t => !t.object.pillar); - if (iup.length && iup.length % 2 === 0) { - let idn = new Raycaster(rp, dn) - .intersectObjects(targets, false) - .filter(t => !t.object.pillar); - if (idn.length) { - iw = iup[0].object.widget || iw; - let wa = api.widgets.annotate(iw.id); - let ws = (wa.support = wa.support || []); - let phi = iup[0].point; - let plo = idn[0].point; - let ply = Math.max(0, plo.y); - let mp = (phi.y + ply) / 2; - let dy = Math.abs(phi.y - ply); - let dw = api.conf.get().process.sliceSupportSize / 2; - let rec = { rz:angle, x:point.x, y:point.z, z:mp, dw, dh:dy, id }; - ws.push(Object.clone(rec)); - fromPillar = addWidgetSupport(iw, rec); - } - } - } - api.event.on("mouse.hover", data => { - if (!isFdmMode) { + if (!(addingSupports && isFdmMode)) { return; } - if (!addingSupports) { - return; - } - delbox('supp'); const { int, type, point, event } = data; - const pillar = int ? int.object.pillar : undefined; - if (lastPillar) { - lastPillar.box.material.color.r = 0; - lastPillar = null; - } - if (pillar) { - pillar.box.material.color.r = 0.5; - lastPillar = pillar; - if (event && (event.metaKey || event.ctrlKey)) { - return func.hoverup(); - } - return; - } - if (int && type === 'widget') { - iw = int.object.widget || iw; - } else { - iw = null; - } - p1 = point; - // looking up by default (assumes platform intersect) - let dir = new Vector3(0,1,0) - let ray = new Raycaster(point, dir); - let rz = int && int.face ? Math.atan2(int.face.normal.y, int.face.normal.x) : 0; - // when intersecting underside of object, project down - if (int && int.face && int.face.normal.z < -0.1) { - dir.y = -1; - } - let targets = api.widgets.meshes().append(space.internals().platform); - let i2 = ray.intersectObjects(targets, false) - .filter(t => !t.object.pillar) // eliminate other pillars - .filter(i => i.distance > 0.1); // false matches close to origin of ray - if (i2.length) { - p2 = i2[0].point; - iw = i2[0].object.widget || iw; - let p1y = Math.max(0, p1.y); - let p2y = Math.max(0, p2.y); - let hy = (p1y + p2y) / 2; - let dy = Math.abs(p1y - p2y); - let dw = api.conf.get().process.sliceSupportSize / 2; - addbox({x:p1.x, y:hy, z:p1.z}, 0x0000dd, 'supp', { - x:dw, y:dw, z:dy, rz - }); - } else { - lastBox = null; - } - if (event && event.altKey) { - return func.hoverup(); - } }); - - api.event.on("export.debug", msg => { - if (msg.arcQ && msg.arcQ.length > 2) { - if (xpdebug) { - xpdebug.destroy(); - xpdebug = undefined; - } - let poly = base.newPolygon().setOpen(); - for (let rec of msg.arcQ) { - rec.z += 1; - poly.addObj(rec); - } - - let layers = new Layers(); - let layer = layers.setLayer("arcq", { line: 0xff00ff, face: 0xff00ff, opacity: 1 }); - layer.addPoly(poly, {thin:true}); - - layer = layers.setLayer("arcc", { line: 0xff0000, face: 0xff0000, opacity: 1 }); - for (let center of msg.arcQ.center) { - center.z += 0.5; - layer.addPoly(base.newPolygon().centerCircle( - center, center.r, 20 - )); - } - - xpdebug = new Stack(space.world, false); - xpdebug.addLayers(layers); - xpdebug.setVisible(0,Infinity); - xpdebug.show(); - - space.refresh(); - - console.log(msg, poly); - } - }); -} - -let xpdebug; -let supsave = {}; -let suptimer; - -function support_generate(ondone) { - client.clear(); - client.sync(); - const settings = api.conf.get(); - const widgets = api.widgets.map(); - client.send("fdm_support_generate", { settings }, (gen) => { - if (gen && gen.error) { - api.show.alert('support generation canceled'); - return ondone([]); - } - for (let g of gen) { - g.widget = widgets[g.id]; - } - ondone(gen); - }); -}; - -function scheduleSupportSave(widget) { - clearTimeout(suptimer); - supsave[widget.id] = widget; - suptimer = setTimeout(() => { - for (let w of Object.values(supsave)) { - w.saveState(); - } - }, 50); -} - -export function restoreSupports(widgets) { - widgets.forEach(widget => { - const supports = api.widgets.annotate(widget.id).support || []; - supports.forEach(pos => { - addWidgetSupport(widget, pos); - }); - }); -} - -function addWidgetSupport(widget, pos) { - const { x, y, z, rz, dw, dh, id } = pos; - const sups = widget.sups = (widget.sups || {}); - // prevent duplicate restore from repeated settings load calls - if (!sups[id]) { - pos.box = addbox( - { x, y, z }, 0x0000dd, id, - { x:dw, y:dw, z:dh, rz }, { group: widget.mesh } - ); - pos.box.pillar = Object.assign({widget}, pos); - sups[id] = pos; - widget.adds.push(pos.box); - scheduleSupportSave(widget); - } - return sups[id]; -} - -function removeWidgetSupport(widget, rec) { - const { box, id } = rec; - widget.adds.remove(box); - widget.mesh.remove(box); - delete widget.sups[id]; - let sa = api.widgets.annotate(widget.id).support; - let ix = 0; - sa.forEach((rec,i) => { - if (rec.id === id) { - ix = i; - } - }); - sa.splice(ix,1); - api.conf.save(); - scheduleSupportSave(widget); - fromPillar = undefined; -} - -function updateSupportVisiblity() { - const visible = (lastMode === 'FDM' && lastView === VIEWS.ARRANGE); - for (let w of api.widgets.all()) { - setSupportVisiblity(w, visible); - } -} - -function setSupportVisiblity(widget, bool) { - Object.values(widget.sups || {}).forEach(support => { - support.box.visible = bool; - }); -} - -function clearAllWidgetSupports() { - let cleared = 0; - api.widgets.all().forEach(widget => { - cleared += clearWidgetSupports(widget); - }); - return cleared; } function clearWidgetSupports(widget) { - let cleared = 0; - Object.values(widget.sups || {}).forEach(support => { - widget.adds.remove(support.box); - widget.mesh.remove(support.box); - cleared++; - }); - widget.sups = {}; - delete api.widgets.annotate(widget.id).support; - scheduleSupportSave(widget); - return cleared; + widget.anno.paint = []; } diff --git a/src/kiri/mode/fdm/post.js b/src/kiri/mode/fdm/post.js index a6b0bec6..299c189c 100644 --- a/src/kiri/mode/fdm/post.js +++ b/src/kiri/mode/fdm/post.js @@ -19,7 +19,7 @@ slicer.slicePost.FDM = slicePost; export function slicePost(data, options) { const { groups, z } = data; const { post_args, useAssembly, zIndexes } = options; - const { isSynth, process, vaseMode } = post_args; + const { process, vaseMode } = post_args; const { compInner, compOuter, pump } = post_args; const { clipOffset, fillOffset, shellOffset } = post_args; @@ -60,20 +60,6 @@ export function slicePost(data, options) { data.tops = POLY.nest([...inner, ...outer]); } - if (isSynth) { - const process = post_args.process; - if (process.sliceSupportGrow > 0) { - // expand synth supports here so they can be clipped later - data.tops = POLY.expand(data.tops, process.sliceSupportGrow, data.z, []); - } - // do not shell synth widgets because - // they will be clipped against peers later - // which requires shelling post-clip - // but we still need to generate tops - delete data.groups; - return; - } - const index = zIndexes.indexOf(z); const range = getRangeParameters(process, index); // calculate fractional shells @@ -87,10 +73,10 @@ export function slicePost(data, options) { let trg = shellFrac > 0.5 ? 1 : parts - 1; sliceShells += rem >= trg ? 1 : 0; } - const count = isSynth ? 1 : sliceShells; + const count = sliceShells; const offset = shellOffset; const fillOff = fillOffset; - const thinType = isSynth ? undefined : process.sliceDetectThin; + const thinType = process.sliceDetectThin; const nutops = []; // co-locate shell processing with top generation in slicer for (let top of data.tops) { diff --git a/src/kiri/mode/fdm/prepare.js b/src/kiri/mode/fdm/prepare.js index 480c1b84..f3d3110e 100644 --- a/src/kiri/mode/fdm/prepare.js +++ b/src/kiri/mode/fdm/prepare.js @@ -9,7 +9,6 @@ import { newPolygon, Polygon } from '../../../geo/polygon.js'; import { polygons as POLY, fillArea } from '../../../geo/polygons.js'; import { newPrint } from '../../core/print.js'; import { newSlice } from '../../core/slice.js'; -import { newWidget } from '../../core/widget.js'; import { render } from '../../core/render.js'; import { getRangeParameters } from './driver.js'; @@ -116,7 +115,7 @@ export async function fdm_prepare(widgets, settings, update) { brims = POLY.expand(brims, -extra, zheight, null, 1); } - // if raft is specified + // create raft when specified if (useRaft) { let offset = newPoint(0,0,0), height = nozzle, @@ -217,39 +216,6 @@ export async function fdm_prepare(widgets, settings, update) { bounds.max.y = Math.max(bounds.max.y, bbounds.maxy); } - // synthesize support widgets when needed - // so that they can use a separate extruder - // compute zmin for belt purge towers - for (let widget of widgets.slice()) { - let sslices = []; - if (!widget.slices) { - console.log('invalid widget', widget); - continue; - } - for (let slice of widget.slices) { - zmin = Math.min(zmin, slice.z); - zmax = Math.max(zmax, slice.z); - if (!slice.supports) { - continue; - } - let sslice = newSlice(slice.z); - sslice.extruder = process.sliceSupportNozzle; - sslice.supports = slice.supports.slice(); - sslice.height = slice.height; - sslices.push(sslice); - } - if (sslices.length) { - let swidget = newWidget(null,widget.group); - swidget.slices = sslices; - swidget.support = true; - swidget.belt = widget.belt; - swidget.track = Object.clone(widget.track); - swidget.mesh = { widget: swidget, position: swidget.track.pos }; - widget.anno.extruder = process.sliceSupportNozzle; - widgets.push(swidget); - } - } - let lastPoly; let lastLayer; let lastOffset; diff --git a/src/kiri/mode/fdm/slice.js b/src/kiri/mode/fdm/slice.js index e549834d..d880e7a5 100644 --- a/src/kiri/mode/fdm/slice.js +++ b/src/kiri/mode/fdm/slice.js @@ -127,16 +127,14 @@ export function fdm_slice(settings, widget, onupdate, ondone) { { devel, assembly, threaded } = controller, isBelt = device.bedBelt, isBrick = devel && process.sliceZInterleave, - isSynth = widget.track.synth, - isSupport = widget.track.support, useAssembly = assembly, isConcurrent = threaded && minions.concurrent && !process.xray, topLayers = process.sliceTopLayers || 0, bottomLayers = process.sliceBottomLayers || 0, - vaseMode = process.sliceFillType === 'vase' && !isSynth, + vaseMode = process.sliceFillType === 'vase', metadata = widget.anno, maxtruder = Math.max(0, device.extruders.length - 1), - extruder = Math.min(maxtruder, parseInt(isSynth ? process.sliceSupportNozzle : metadata.extruder || 0)), + extruder = Math.min(maxtruder, parseInt(metadata.extruder || 0)), sliceHeight = process.sliceHeight, sliceHeightBase = (isBelt ? sliceHeight : process.firstSliceHeight) || sliceHeight, lineWidth = process.sliceLineWidth || device.extruders[extruder].extNozzle, @@ -233,7 +231,7 @@ export function fdm_slice(settings, widget, onupdate, ondone) { zMin: bounds.min.z, zMax: bounds.max.z - zCut, // support/synth usually has overlapping boxes - union: controller.healMesh || isSynth, + union: controller.healMesh, indices: process.indices || process.xray, useAssembly, post: 'FDM', @@ -245,7 +243,6 @@ export function fdm_slice(settings, widget, onupdate, ondone) { clipOffset, lineWidth, vaseMode, - isSynth, process, }, // z index generator @@ -331,7 +328,6 @@ export function fdm_slice(settings, widget, onupdate, ondone) { }, // slicer function (worker local or minion distributed) slicer(z, points, opts) { - // opts.debug = opts.debug || isSynth; return (isConcurrent ? minions.sliceZ : sliceZ)(z, points, opts); }, onupdate(update) { @@ -347,7 +343,7 @@ export function fdm_slice(settings, widget, onupdate, ondone) { slice.height = heights[slice.index]; slice.clips = clip; // do not warn on merging supports - if (changes && !isSynth) { + if (changes) { healed = true; slice.changes = changes; if (devel) { @@ -431,49 +427,10 @@ export function fdm_slice(settings, widget, onupdate, ondone) { } // create shadow for non-belt supports - if (!isBelt && (isSynth || (!isSynth && supportDensity && process.sliceSupportEnable))) { + if (!isBelt && supportDensity && process.sliceSupportEnable) { await doShadow(slices); } - // for synth support widgets, clip/offset to other widgets in group - if (isSynth) { - for (let slice of slices) { - let gap = sliceHeight * process.sliceSupportGap; - // clip tops to other widgets in group - let tops = slice.topPolys(); - for (let peer of widget.group) { - // skip self - if (peer === widget || !peer.slices) { - continue; - } - for (let pslice of peer.slices) { - if (Math.abs(Math.abs(pslice.z - slice.z) - gap) > 0.1) { - continue; - } - let clipto = pslice.clips; - if (pslice.supportOutline) { - // merge support outlines into slice clips which - // should only happen when automatic and manual - // supports are used together - clipto.appendAll(pslice.supportOutline); - } - let ntops = []; - POLY.subtract(tops, clipto, ntops, null, slice.z, 0); - tops = ntops; - } - // trim to group's shadow if not in belt mode - if (!isBelt) { - tops = POLY.setZ(POLY.trimTo(tops, widget.shadow), slice.z); - } - } - slice.tops = []; - for (let t of tops) { - slice.addTop(t); - } - doShells(slice, 1, shellOffset / 2); - } - } - // calculate % complete and call onupdate() function doupdate(index, from, to, msg) { trackupdate(index / slices.length, from, to, msg); @@ -509,7 +466,7 @@ export function fdm_slice(settings, widget, onupdate, ondone) { let isTop = topLayers && slice.index > slices.length - topLayers - 1; let isTopBase = isTop && slice.index === slices.length - topLayers; let isDense = range.sliceFillSparse > 0.995; - let isSolid = (isBottom || ((isTop || isDense) && !vaseMode)) && !isSynth; + let isSolid = (isBottom || ((isTop || isDense) && !vaseMode)); let solidWidth = isSolid ? range.sliceFillWidth || 1 : 0; if (solidWidth) { let fillSpace = fillSpacing * solidWidth; @@ -526,7 +483,7 @@ export function fdm_slice(settings, widget, onupdate, ondone) { }, "solid layers"); // add lead in anchor when specified in belt mode (but not for synths) - if (isBelt && !isSynth) { + if (isBelt) { let { cosf, slope } = widget.belt; // find adjusted zero point from slices let smin = Infinity; @@ -737,7 +694,7 @@ export function fdm_slice(settings, widget, onupdate, ondone) { // layer boolean diffs need to be computed to find flat areas to fill // and overhangs that need to be supported. these are stored in flats // and bridges, projected up/down, and merged into an array of solids - if (!vaseMode && !isSynth) { + if (!vaseMode) { profileStart("delta"); forSlices(0.2, 0.34, slice => { let params = slice.params || process; @@ -778,7 +735,7 @@ export function fdm_slice(settings, widget, onupdate, ondone) { // for "real" objects, fill the remaining voids with sparse fill // sparse layers only present when non-vase mose and sparse % > 0 - if (!isSynth && !vaseMode) { + if (!vaseMode) { let lastType; let promises = isConcurrent ? [] : undefined; forSlices(0.5, promises ? 0.55 : 0.7, slice => { @@ -817,44 +774,10 @@ export function fdm_slice(settings, widget, onupdate, ondone) { } } } - } else if (isSynth && isSupport) { - // convert synth support widgets into support structure - let outline = process.sliceSupportOutline || false; - let promises = isConcurrent ? [] : undefined; - let resolve = []; - forSlices(0.5, promises ? 0.6 : 0.7, slice => { - let params = slice.params || process; - let density = params.sliceSupportDensity; - let supports = slice.topShells(); - slice.supports = supports; - slice.tops = undefined; // remove outline leave only supports - let polys = []; - if (density) { - if (!outline) { - POLY.expand(supports, lineWidth, slice.z, polys); - } else { - POLY.expand(supports, -lineWidth/4, slice.z, polys); - } - } - fillSupportPolys({ - promises, polys, lineWidth, density, z: slice.z, isBelt, - angle: process.sliceSupportFill - }); - resolve.push({ slice, polys }); - }, "infill"); - if (promises) { - await tracker(promises, (i, t) => { - trackupdate(i / t, 0.6, 0.7); - }); - } - for (let rec of resolve) { - let { slice, polys } = rec; - rec.supports = polys; - } } // auto support generation - if (!isBelt && !isSynth && supportDensity && process.sliceSupportEnable) { + if (!isBelt && supportDensity && process.sliceSupportEnable) { doShadow(slices); profileStart("support"); let promises = []; @@ -940,7 +863,7 @@ export function fdm_slice(settings, widget, onupdate, ondone) { if (render) { forSlices(0.9, 1.0, slice => { let params = slice.params || process; - doRender(slice, isSynth, params, { + doRender(slice, params, { dark: controller.dark, devel: controller.devel }); @@ -999,7 +922,7 @@ function bound(v,min,max) { return Math.max(min,Math.min(max,v)); } -function doRender(slice, isSynth, params, opt = {}) { +function doRender(slice, params, opt = {}) { const { dark, devel } = opt; const Color = dark ? COLOR_DARK : COLOR; const output = slice.output(); @@ -1013,7 +936,7 @@ function doRender(slice, isSynth, params, opt = {}) { .addPolys([top.poly]); if (top.shells?.length) output - .setLayer(isSynth ? "support" : "shells", isSynth ? Color.support : Color.shell) + .setLayer("shells", Color.shell) .addPolys(top.shells || [], vopt({ offset, height, clean: true })); if (isThin && top.thin_wall?.length) output @@ -1031,7 +954,7 @@ function doRender(slice, isSynth, params, opt = {}) { ), vopt({ offset, height })); if (top.fill_lines?.length) output - .setLayer("solid fill", isSynth ? Color.support : Color.fill) + .setLayer("solid fill", Color.fill) .addLines(top.fill_lines || [], vopt({ offset: offset * solidWidth, height, z:slice.z })); if (!(slice.belt?.anchor)) output @@ -1783,103 +1706,3 @@ function projectSolid(slice, polys, count, up, first) { else projectSolid(slice.down, polys, count-1, false, false); } } - -export function supports(settings, widget) { - let isBelt = settings.device.bedBelt; - let process = settings.process; - let size = process.sliceSupportSize; - let s9 = size / 9; - let s4 = size / 4; - let s2 = size * 0.45; - let min = 0.01; - let geo = new THREE.BufferGeometry(); - geo.setAttribute('position', new THREE.BufferAttribute(widget.vertices, 3)); - let rad = (Math.PI / 180); - let deg = (180 / Math.PI); - let angle = rad * settings.process.sliceSupportAngle; - let thresh = -Math.sin(angle); - let dir = new THREE.Vector3(0,0,-1) - let add = []; - let mat = new THREE.MeshBasicMaterial(); - let mesh = new THREE.Mesh(geo, mat); - let platform = new THREE.Mesh( - new THREE.PlaneGeometry(10000,10000,1), mat - ); - const now = Date.now(); - // test point - function tp(point) { - if (point.added) { - return; - } - // omit pillars close to existing pillars - for (let added of add) { - let p2 = new THREE.Vector2(point.x, point.y); - let pm = new THREE.Vector2(added.mid.x, added.mid.y); - if (Math.abs(point.z - added.from.z) < s2 && p2.distanceTo(pm) < s4) { - return; - } - } - let ray = new THREE.Raycaster(point, dir); - let int = ray.intersectObjects([ mesh, platform ], false); - if (int && int.length && int[0].distance > 0.5) { - let mid = new THREE.Vector3().add(point).add(int[0].point).divideScalar(2); - add.push({from: point, to: int[0].point, mid}); - point.added = true; - } - } - function tf(a, b, c) { - let dab = a.distanceTo(b); - let dbc = b.distanceTo(c); - let dca = c.distanceTo(a); - let max = Math.max(dab, dbc, dca); - if (max < size) { - let min = Math.min(dab, dbc, dca); - if (min < s9 && Math.random() < 0.5) { - return; - } - // test midpoint of tri face - return tp(new THREE.Vector3().add(a).add(b).add(c).divideScalar(3)); - } - if (dab === max) { - let mp = new THREE.Vector3().add(a).add(b).divideScalar(2); - tf(mp, b, c); - tf(a, mp, c); - } else if (dbc === max) { - let mp = new THREE.Vector3().add(b).add(c).divideScalar(2); - tf(a, mp, c); - tf(a, b, mp); - } else { - let mp = new THREE.Vector3().add(c).add(a).divideScalar(2); - tf(a, b, mp); - tf(mp, b, c); - } - } - - let filter = isBelt ? (norm) => { - return norm.z <= thresh && norm.y < 0; - } : (norm) => { - return norm.z < thresh; - }; - let { position } = geo.attributes; - let { itemSize, count, array } = position; - for (let i = 0; i 0; -} diff --git a/src/moto/space.js b/src/moto/space.js index 90122faf..b74074f0 100644 --- a/src/moto/space.js +++ b/src/moto/space.js @@ -969,7 +969,7 @@ function onMouseUp(event) { function onMouseMove(event) { updateLastAction(); - let int, vis; + let int, vis, dragTrack; const mv = new THREE.Vector2(); mv.x = ( event.clientX / window.innerWidth ) * 2 - 1; @@ -990,13 +990,18 @@ function onMouseMove(event) { platform.visible = vis; if (int && int.length > 0) platformHover(int[0].point); } - } else if (mouseDragPoint && mouseDrag && mouseDrag()) { + } else if (mouseDragPoint && mouseDrag && (dragTrack = mouseDrag())) { event.preventDefault(); let trackTo = alignedTracking ? trackPlane : platform; let vis = trackTo.visible; trackTo.visible = true; - int = intersect([trackTo], false); - trackTo.visible = vis; + if (dragTrack.length) { + int = intersect(dragTrack, false); + trackTo = dragTrack[0]; + } else { + int = intersect([trackTo], false); + trackTo.visible = vis; + } if (int.length > 0 && int[0].object === trackTo) { let delta = mouseDragPoint.clone().sub(int[0].point); let offset = mouseDragStart.clone().sub(int[0].point); @@ -1010,7 +1015,7 @@ function onMouseMove(event) { x: -offset.x, y: offset.z, z: 0 - }); + }, false, int); requestRefresh(); } }