From 22da074965354fd7ec23e6b59473a06485832bd4 Mon Sep 17 00:00:00 2001 From: Kurt Hutten Date: Wed, 8 Sep 2021 06:16:52 +1000 Subject: [PATCH 1/2] Move worker into webpack build The jscad worker code was hosted as a static asset, making it odd javascript where we have to be conscious of what javascript features we can use and if it will work on older browsers, plus it can't be typescript like the rest of the codebase. Since redwood 0.36 we using webpack 5 should make loading workers easy https://webpack.js.org/guides/web-workers/ But I had trouble with this (see: https://community.redwoodjs.com/t/has-anyone-tried-workers-with-webpack-5-rw0-36-x/2394) and instead used the webpack 4 loader without any issues This issue relates to #411 , and is a checklist item on #444 Resolves #494 --- app/web/config/worker-loader.d.ts | 10 + app/web/package.json | 5 +- .../cadPackages/jsCad/jsCadController.tsx | 15 +- .../helpers/cadPackages/jsCad/jscadWorker.ts} | 184 +++--------------- app/yarn.lock | 8 + 5 files changed, 53 insertions(+), 169 deletions(-) create mode 100644 app/web/config/worker-loader.d.ts rename app/web/{public/demo-worker.js => src/helpers/cadPackages/jsCad/jscadWorker.ts} (78%) diff --git a/app/web/config/worker-loader.d.ts b/app/web/config/worker-loader.d.ts new file mode 100644 index 0000000..cf7fd6b --- /dev/null +++ b/app/web/config/worker-loader.d.ts @@ -0,0 +1,10 @@ +declare module "worker-loader!*" { + // You need to change `Worker`, if you specified a different value for the `workerType` option + class WebpackWorker extends Worker { + constructor(); + } + + // Uncomment this if you set the `esModule` option to `false` + // export = WebpackWorker; + export default WebpackWorker; +} diff --git a/app/web/package.json b/app/web/package.json index 76b4728..77285a4 100644 --- a/app/web/package.json +++ b/app/web/package.json @@ -45,7 +45,8 @@ "react-tabs": "^3.2.2", "rich-markdown-editor": "^11.0.2", "styled-components": "^5.2.0", - "three": "^0.130.1" + "three": "^0.130.1", + "worker-loader": "^3.0.8" }, "devDependencies": { "@types/lodash": "^4.14.170", @@ -56,4 +57,4 @@ "postcss-loader": "^6.1.1", "tailwindcss": "^2.2.7" } -} \ No newline at end of file +} diff --git a/app/web/src/helpers/cadPackages/jsCad/jsCadController.tsx b/app/web/src/helpers/cadPackages/jsCad/jsCadController.tsx index 4f8c2f2..5562de7 100644 --- a/app/web/src/helpers/cadPackages/jsCad/jsCadController.tsx +++ b/app/web/src/helpers/cadPackages/jsCad/jsCadController.tsx @@ -16,6 +16,7 @@ import { Mesh, } from 'three' import { jsCadToCadhubParams } from './jscadParams' +import TheWorker from 'worker-loader!./jscadWorker' const materials = { mesh: { @@ -134,19 +135,7 @@ export const render: DefaultKernelExport['render'] = async ({ }: RenderArgs) => { if (!scriptWorker) { const baseURI = document.baseURI.toString() - const scriptUrl = '/demo-worker.js' - const script = `let baseURI = '${baseURI}' - importScripts(new URL('${scriptUrl}',baseURI)) - let worker = jscadWorker({ - baseURI: baseURI, - scope:'worker', - convertToSolids: 'buffers', - callback:(params)=>self.postMessage(params), - }) - self.addEventListener('message', (e)=>worker.postMessage(e.data)) - ` - const blob = new Blob([script], { type: 'text/javascript' }) - scriptWorker = new Worker(window.URL.createObjectURL(blob)) + scriptWorker = new TheWorker() let parameterDefinitions = [] scriptWorker.addEventListener('message', ({ data }) => { if (data.action == 'parameterDefinitions') { diff --git a/app/web/public/demo-worker.js b/app/web/src/helpers/cadPackages/jsCad/jscadWorker.ts similarity index 78% rename from app/web/public/demo-worker.js rename to app/web/src/helpers/cadPackages/jsCad/jscadWorker.ts index f49e365..283f28a 100644 --- a/app/web/public/demo-worker.js +++ b/app/web/src/helpers/cadPackages/jsCad/jscadWorker.ts @@ -1,25 +1,3 @@ -(function(f) { - if (typeof exports === "object" && typeof module !== "undefined") { - module.exports = f() - } else if (typeof define === "function" && define.amd) { - define([], f) - } else { - var g; - if (typeof window !== "undefined") { - g = window - } else if (typeof global !== "undefined") { - g = global - } else if (typeof self !== "undefined") { - g = self - } else { - g = this - } - g.jscadWorker = f() - } -})(function() { -// multi purpose module - - const setPoints = (points, p, i)=>{ points[i++] = p[0] @@ -176,56 +154,6 @@ require.cache = {} require.alias = {} -const initCanvas = (canvas, callback)=>{ - - // convert HTML events (mouse movement) to viewer changes - let lastX = 0 - let lastY = 0 - - let pointerDown = false - - const moveHandler = (ev) => { - if(!pointerDown) return - const cmd = { - worker: 'render', - dx: lastX - ev.pageX, - dy: ev.pageY - lastY - } - - const shiftKey = (ev.shiftKey === true) || (ev.touches && ev.touches.length > 2) - cmd.action = shiftKey ? 'pan':'rotate' - callback(cmd) - - lastX = ev.pageX - lastY = ev.pageY - - ev.preventDefault() - } - const downHandler = (ev) => { - pointerDown = true - lastX = ev.pageX - lastY = ev.pageY - canvas.setPointerCapture(ev.pointerId) - ev.preventDefault() - } - - const upHandler = (ev) => { - pointerDown = false - canvas.releasePointerCapture(ev.pointerId) - ev.preventDefault() - } - - const wheelHandler = (ev) => { - callback({action:'zoom', dy:ev.deltaY, worker: 'render'}) - ev.preventDefault() - } - - canvas.onpointermove = moveHandler - canvas.onpointerdown = downHandler - canvas.onpointerup = upHandler - canvas.onwheel = wheelHandler -} - const cmdHandler = (handlers)=>(cmd)=>{ const fn = handlers[cmd.action] if (!fn) throw new Error('no handler for type: ' + cmd.action) @@ -239,7 +167,7 @@ function parseParams(script){ let lines = script.split('\n').map(l=>l.trim()) lines = lines.map((l,i)=>{ - return {code:l, line:i+1, group: l[0] == '/' && !lines[i+1]} + return {code:l, line:i+1, group: l[0] == '/' && !lines[i+1]} }).filter(l=>l.code) let i = 0, line, next, lineNum @@ -322,13 +250,13 @@ function parseComment(comment, line){ const ret = {} const idx = comment.indexOf('{') if(idx !== -1){ - try{ - ret.options = eval('('+comment.substring(idx)+')') - }catch(e){ - console.log('Error in line '+line); - console.log(comment); - throw e - } + try{ + ret.options = eval('('+comment.substring(idx)+')') + }catch(e){ + console.log('Error in line '+line); + console.log(comment); + throw e + } comment = comment.substring(0,idx).trim() } @@ -365,8 +293,8 @@ function parseDef(code, line){ try { ret.initial = eval(initial) } catch (e) { - console.log('Error in line '+line); - console.log(code); + console.log('Error in line '+line); + console.log(code); console.log('problem evaluating inital value:', initial) e = new EvalError(e.message, 'code', line); e.lineNumber = line @@ -380,9 +308,8 @@ function parseDef(code, line){ - const makeScriptWorker = ({callback, convertToSolids})=>{ - let workerBaseURI, onInit + let onInit, main, scriptStats, entities function runMain(params={}){ @@ -695,92 +622,41 @@ let perspectiveCamera } - - - - - - - - -return (params)=>{ - let { canvas, baseURI=(typeof document === 'undefined') ? '':document.location.toString(), scope='main', renderInWorker, render, callback=()=>{}, scriptUrl='demo-worker.js', alias, convertToSolids=false } = params +function start(params) { + let { + callback=()=>{}, + convertToSolids=false + } = params // by default 'render' messages go outside of this instance (result of modeling) - let sendToRender = callback - let scriptWorker, renderWorker - workerBaseURI = baseURI + let scriptWorker const sendCmd = (params, transfer)=>{ - if(params.worker === 'render') - sendToRender(params, transfer) - else if(params.worker === 'script') - scriptWorker.postMessage(params, transfer) + if(params.worker === 'script') scriptWorker.postMessage(params, transfer) else{ - // parameter definitions will arrive from scriptWorker callback(params, transfer) } } - const updateSize = function({width,height}){ - sendCmd({ action:'resize', worker:'render', width: canvas.offsetWidth, height: canvas.offsetHeight}) - } - renderInWorker = !!(canvas && renderInWorker && canvas.transferControlToOffscreen) - const makeRenderWorkerHere = (scope === 'main' && canvas && !renderInWorker) || (scope === 'worker' && render) - // worker is in current thread - if(makeRenderWorkerHere){ - renderWorker = makeRenderWorker({callback:sendCmd}) - sendToRender = (params, transfer)=>renderWorker.postMessage(params, transfer) - } - - if(scope === 'main'){ -// let extraScript = renderInWorker ? `,'https://unpkg.com/@jscad/regl-renderer'`:'' - let script =`let baseURI = '${baseURI}' -importScripts(new URL('${scriptUrl}',baseURI)) -let worker = jscadWorker({ - baseURI: baseURI, - convertToSolids: ${convertToSolids}, - scope:'worker', - callback:(params)=>self.postMessage(params), - render:${renderInWorker} -}) -self.addEventListener('message', (e)=>worker.postMessage(e.data)) -` - let blob = new Blob([script],{type: 'text/javascript'}) - scriptWorker = new Worker(window.URL.createObjectURL(blob)) - scriptWorker.addEventListener('message',(e)=>sendCmd(e.data)) - scriptWorker.postMessage({action:'init', baseURI, alias}) - if(renderInWorker) renderWorker = scriptWorker - - if(canvas){ - initCanvas(canvas, sendCmd) - window.addEventListener('resize',updateSize) - } - }else{ - scriptWorker = makeScriptWorker({callback:sendCmd, convertToSolids}) - callback({action:'workerInit',worker:'main'}) - } - - if(canvas){ - // redirect 'render' messages to renderWorker - sendToRender = (params, transfer)=>renderWorker.postMessage(params, transfer) - let width = canvas.width = canvas.clientWidth - let height = canvas.height = canvas.clientHeight - if(scope == 'main'){ - const offscreen = renderInWorker ? canvas.transferControlToOffscreen() : canvas - renderWorker.postMessage({action:'init', worker:'render', canvas:offscreen, width, height}, [offscreen]) - } - } + scriptWorker = makeScriptWorker({callback:sendCmd, convertToSolids}) + callback({action:'workerInit',worker:'main'}) return { - updateSize, updateParams:({params={}})=>sendCmd({ action:'updateParams', worker:'script', params}), runScript: ({script,url=''})=>sendCmd({ action:'runScript', worker:'script', script, url}), postMessage: sendCmd, } } +const init = start({ + convertToSolids: 'buffers', + callback:(params)=>self.postMessage(params), +}) -// multi purpose module -}); +self.onmessage = ({data}) => { + if (data.action === 'init') { + workerBaseURI = data.baseURI + } + init.postMessage(data, null) +}; diff --git a/app/yarn.lock b/app/yarn.lock index 3adfcb0..f75c025 100644 --- a/app/yarn.lock +++ b/app/yarn.lock @@ -19057,6 +19057,14 @@ worker-farm@^1.7.0: dependencies: errno "~0.1.7" +worker-loader@^3.0.8: + version "3.0.8" + resolved "https://registry.yarnpkg.com/worker-loader/-/worker-loader-3.0.8.tgz#5fc5cda4a3d3163d9c274a4e3a811ce8b60dbb37" + integrity sha512-XQyQkIFeRVC7f7uRhFdNMe/iJOdO6zxAaR3EWbDp45v3mDhrTi+++oswKNxShUNjPC/1xUp5DB29YKLhFo129g== + dependencies: + loader-utils "^2.0.0" + schema-utils "^3.0.0" + worker-rpc@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/worker-rpc/-/worker-rpc-0.1.1.tgz#cb565bd6d7071a8f16660686051e969ad32f54d5" From 58b618cf5f511c5a9bb665d924de5904a055df58 Mon Sep 17 00:00:00 2001 From: Kurt Hutten Date: Wed, 8 Sep 2021 06:18:11 +1000 Subject: [PATCH 2/2] format jscad worker --- .../helpers/cadPackages/jsCad/jscadWorker.ts | 779 +++++++++--------- 1 file changed, 406 insertions(+), 373 deletions(-) diff --git a/app/web/src/helpers/cadPackages/jsCad/jscadWorker.ts b/app/web/src/helpers/cadPackages/jsCad/jscadWorker.ts index 283f28a..3f8f6a1 100644 --- a/app/web/src/helpers/cadPackages/jsCad/jscadWorker.ts +++ b/app/web/src/helpers/cadPackages/jsCad/jscadWorker.ts @@ -1,410 +1,440 @@ - -const setPoints = (points, p, i)=>{ +const setPoints = (points, p, i) => { points[i++] = p[0] points[i++] = p[1] points[i++] = p[2] || 0 } -function CSG2Vertices(csg){ - let idx = 0 +function CSG2Vertices(csg) { + const idx = 0 - let vLen = 0, iLen = 0 - for (let poly of csg.polygons){ - let len = poly.vertices.length - vLen += len *3 - iLen += 3 * (len-2) - } - const vertices = new Float32Array(vLen) - const indices = vLen > 65535 ? new Uint32Array(iLen) : new Uint16Array(iLen) - - let vertOffset = 0 - let indOffset = 0 - let posOffset = 0 - let first = 0 - for (let poly of csg.polygons){ - let arr = poly.vertices - let len = arr.length - first = posOffset - vertices.set(arr[0], vertOffset) - vertOffset +=3 - vertices.set(arr[1], vertOffset) - vertOffset +=3 - posOffset +=2 - for(let i=2; isetPoints(vertices, p, idx * 3 )) - - if(csg.isClosed){ - setPoints(vertices, csg.points[0], vertices.length - 3 ) + let vLen = 0, + iLen = 0 + for (const poly of csg.polygons) { + const len = poly.vertices.length + vLen += len * 3 + iLen += 3 * (len - 2) } - return {vertices, type:'line'} + const vertices = new Float32Array(vLen) + const indices = vLen > 65535 ? new Uint32Array(iLen) : new Uint16Array(iLen) + + let vertOffset = 0 + let indOffset = 0 + let posOffset = 0 + let first = 0 + for (const poly of csg.polygons) { + const arr = poly.vertices + const len = arr.length + first = posOffset + vertices.set(arr[0], vertOffset) + vertOffset += 3 + vertices.set(arr[1], vertOffset) + vertOffset += 3 + posOffset += 2 + for (let i = 2; i < len; i++) { + vertices.set(arr[i], vertOffset) + + indices[indOffset++] = first + indices[indOffset++] = first + i - 1 + indices[indOffset++] = first + i + + vertOffset += 3 + posOffset += 1 + } + } + return { vertices, indices, type: 'mesh' } } -function CSG2LineSegmentsVertices(csg){ - let vLen = csg.sides.length * 6 +function CSG2LineVertices(csg) { + let vLen = csg.points.length * 3 + if (csg.isClosed) vLen += 3 - var vertices = new Float32Array(vLen) - csg.sides.forEach((side,idx)=>{ - let i = idx * 6 + const vertices = new Float32Array(vLen) + + csg.points.forEach((p, idx) => setPoints(vertices, p, idx * 3)) + + if (csg.isClosed) { + setPoints(vertices, csg.points[0], vertices.length - 3) + } + return { vertices, type: 'line' } +} + +function CSG2LineSegmentsVertices(csg) { + const vLen = csg.sides.length * 6 + + const vertices = new Float32Array(vLen) + csg.sides.forEach((side, idx) => { + const i = idx * 6 setPoints(vertices, side[0], i) - setPoints(vertices, side[1], i+3) + setPoints(vertices, side[1], i + 3) }) - return {vertices, type:'lines'} - + return { vertices, type: 'lines' } } -function CSGCached(func, data, cacheKey, transferable){ +function CSGCached(func, data, cacheKey, transferable) { cacheKey = cacheKey || data let geo = CSGToBuffers.cache.get(cacheKey) - if(geo) return geo + if (geo) return geo geo = func(data) // fill transferable array for postMessage optimization - if(transferable){ - const {vertices, indices} = geo + if (transferable) { + const { vertices, indices } = geo transferable.push(vertices) - if(indices) transferable.push(indices) + if (indices) transferable.push(indices) } - CSGToBuffers.cache.set(cacheKey, geo) - return geo + CSGToBuffers.cache.set(cacheKey, geo) + return geo } -function CSGToBuffers(csg, transferable){ +function CSGToBuffers(csg, transferable) { let obj - if(csg.polygons) obj = CSGCached(CSG2Vertices,csg,csg.polygons, transferable) - if(csg.sides && !csg.points) obj = CSGCached(CSG2LineSegmentsVertices,csg,csg.sides, transferable) - if(csg.points) obj = CSGCached(CSG2LineVertices,csg,csg.points, transferable) + if (csg.polygons) + obj = CSGCached(CSG2Vertices, csg, csg.polygons, transferable) + if (csg.sides && !csg.points) + obj = CSGCached(CSG2LineSegmentsVertices, csg, csg.sides, transferable) + if (csg.points) + obj = CSGCached(CSG2LineVertices, csg, csg.points, transferable) return obj } -CSGToBuffers.clearCache = ()=>{CSGToBuffers.cache = new WeakMap()} +CSGToBuffers.clearCache = () => { + CSGToBuffers.cache = new WeakMap() +} CSGToBuffers.clearCache() - - - let workerBaseURI -function require(url){ - url = require.alias[url] || url - if(url[0] != '/' && url.substr(0,2) != './' && url.substr(0,4) != 'http') url = 'https://unpkg.com/'+url - let exports=require.cache[url]; //get from cache - if (!exports) { //not cached - let module = requireModule(url) - require.cache[url] = exports = module.exports; //cache obj exported by module - } - return exports; //require returns object exported by module +function require(url) { + url = require.alias[url] || url + if (url[0] != '/' && url.substr(0, 2) != './' && url.substr(0, 4) != 'http') + url = 'https://unpkg.com/' + url + let exports = require.cache[url] //get from cache + if (!exports) { + //not cached + const module = requireModule(url) + require.cache[url] = exports = module.exports //cache obj exported by module + } + return exports //require returns object exported by module } -function requireFile(url){ - try{ - let X=new XMLHttpRequest(); - X.open("GET", new URL(url,workerBaseURI), 0); // sync - X.send(); - if (X.status && X.status !== 200) throw new Error(X.statusText); - return X.responseText; - }catch(e){ - console.log('problem loading url ',url,'base',workerBaseURI,' error:',e.message) +function requireFile(url) { + try { + const X = new XMLHttpRequest() + X.open('GET', new URL(url, workerBaseURI), 0) // sync + X.send() + if (X.status && X.status !== 200) throw new Error(X.statusText) + return X.responseText + } catch (e) { + console.log( + 'problem loading url ', + url, + 'base', + workerBaseURI, + ' error:', + e.message + ) throw e } } -function requireModule(url, source){ - try { - const exports={}; - if(!source) source = requireFile(url) - const module = { id: url, uri: url, exports:exports, source }; //according to node.js modules - // fix, add comment to show source on Chrome Dev Tools - source="//@ sourceURL="+url+"\n" + source; - //------ - const anonFn = new Function("require", "exports", "module", source); //create a Fn with module code, and 3 params: require, exports & module - anonFn(require, exports, module); // call the Fn, Execute the module - return module - } catch (err) { - console.error("Error loading module "+url, err.message); - throw err; - } +function requireModule(url, source) { + try { + const exports = {} + if (!source) source = requireFile(url) + const module = { id: url, uri: url, exports: exports, source } //according to node.js modules + // fix, add comment to show source on Chrome Dev Tools + source = '//@ sourceURL=' + url + '\n' + source + //------ + const anonFn = new Function('require', 'exports', 'module', source) //create a Fn with module code, and 3 params: require, exports & module + anonFn(require, exports, module) // call the Fn, Execute the module + return module + } catch (err) { + console.error('Error loading module ' + url, err.message) + throw err + } } require.cache = {} require.alias = {} - -const cmdHandler = (handlers)=>(cmd)=>{ +const cmdHandler = (handlers) => (cmd) => { const fn = handlers[cmd.action] if (!fn) throw new Error('no handler for type: ' + cmd.action) - fn(cmd); + fn(cmd) } +function parseParams(script) { + let lines = script.split('\n').map((l) => l.trim()) + lines = lines + .map((l, i) => { + return { code: l, line: i + 1, group: l[0] == '/' && !lines[i + 1] } + }) + .filter((l) => l.code) + let i = 0, + line, + next, + lineNum + while (i < lines.length) { + line = lines[i].code.trim() + i++ + if (line.length > 12 && line.substring(line.length - 13) == '//jscadparams') + break + if (line.length > 12 && line.indexOf('@jscad-params') != -1) break + } -function parseParams(script){ - let lines = script.split('\n').map(l=>l.trim()) + let groupIndex = 1 + const defs = [] - lines = lines.map((l,i)=>{ - return {code:l, line:i+1, group: l[0] == '/' && !lines[i+1]} - }).filter(l=>l.code) + while (i < lines.length) { + line = lines[i].code + lineNum = lines[i].line + next = lines[i + 1] ? lines[i + 1].code : '' + if (line[0] === '}') break - let i = 0, line, next, lineNum - while(i12 && line.substring(line.length-13) == '//jscadparams') break; - if(line.length>12 && line.indexOf('@jscad-params') != -1) break; + if (line[0] === '/') { + // group + const def = parseComment(line, lineNum) + let name = '_group_' + groupIndex++ + let caption = def.caption + + const idx = caption.lastIndexOf(':') + if (idx !== -1) { + name = caption.substring(idx + 1).trim() + caption = caption.substring(0, idx).trim() + } + defs.push({ name, type: 'group', caption, ...def.options }) + } else { + const idx = line.indexOf('/') + if (idx === -1) { + const def = parseDef(line, lineNum) + def.caption = def.name + defs.push(def) + } else { + defs.push( + parseOne( + line.substring(idx).trim(), + line.substring(0, idx).trim(), + lineNum, + lineNum + ) + ) + } } + i++ + } - let groupIndex = 1 - const defs = [] - - while(i{ +const makeScriptWorker = ({ callback, convertToSolids }) => { let onInit, main, scriptStats, entities - - function runMain(params={}){ + function runMain(params = {}) { let time = Date.now() let solids - let transfer = [] - try{ - let tmp = main(params) + const transfer = [] + try { + const tmp = main(params) solids = [] - function flatten(arr){ - if(arr){ - if(arr instanceof Array) - arr.forEach(flatten) - else - solids.push(arr) + function flatten(arr) { + if (arr) { + if (arr instanceof Array) arr.forEach(flatten) + else solids.push(arr) } } flatten(tmp) - - }catch(e){ - callback({action:'entities', worker:'render', error:e.message, stack:e.stack.toString()}, transfer) + } catch (e) { + callback( + { + action: 'entities', + worker: 'render', + error: e.message, + stack: e.stack.toString(), + }, + transfer + ) return } - let solidsTime = Date.now() - time + const solidsTime = Date.now() - time scriptStats = `generate solids ${solidsTime}ms` - if(convertToSolids === 'buffers'){ + if (convertToSolids === 'buffers') { CSGToBuffers.clearCache() - entities = solids.filter(s=>s).map((csg)=>{ - let obj = CSGToBuffers(csg, transfer) - obj.color = csg.color - obj.transforms = csg.transforms - return obj - }) - }else if(convertToSolids === 'regl'){ + entities = solids + .filter((s) => s) + .map((csg) => { + const obj = CSGToBuffers(csg, transfer) + obj.color = csg.color + obj.transforms = csg.transforms + return obj + }) + } else if (convertToSolids === 'regl') { const { entitiesFromSolids } = require('@jscad/regl-renderer') time = Date.now() entities = entitiesFromSolids({}, solids) - scriptStats += ` convert to entities ${Date.now()-time}ms` - }else{ + scriptStats += ` convert to entities ${Date.now() - time}ms` + } else { entities = solids } - callback({action:'entities', worker:'render', entities, scriptStats}, transfer) + callback( + { action: 'entities', worker: 'render', entities, scriptStats }, + transfer + ) } let initialized = false const handlers = { - runScript: ({script,url, params={}})=>{ - if(!initialized){ - onInit = ()=>handlers.runScript({script,url, params}) + runScript: ({ script, url, params = {} }) => { + if (!initialized) { + onInit = () => handlers.runScript({ script, url, params }) } let script_module - try{ - script_module = requireModule(url,script) - }catch(e){ - callback({action:'entities', worker:'render', error:e.message, stack:e.stack.toString()}) + try { + script_module = requireModule(url, script) + } catch (e) { + callback({ + action: 'entities', + worker: 'render', + error: e.message, + stack: e.stack.toString(), + }) return } main = script_module.exports.main - let gp = script_module.exports.getParameterDefinitions - let paramsDef = parseParams(script) || [] - if(gp){ - gp().forEach(p=>{ - let idx = paramsDef.findIndex(old=>old.name == p.name) - if(idx === -1){ + const gp = script_module.exports.getParameterDefinitions + const paramsDef = parseParams(script) || [] + if (gp) { + gp().forEach((p) => { + const idx = paramsDef.findIndex((old) => old.name == p.name) + if (idx === -1) { paramsDef.push(p) - }else{ - paramsDef.splice(idx,1,p) + } else { + paramsDef.splice(idx, 1, p) } }) } - if(paramsDef.length) callback({action:'parameterDefinitions', worker:'main', data:paramsDef}) + if (paramsDef.length) + callback({ + action: 'parameterDefinitions', + worker: 'main', + data: paramsDef, + }) runMain(params) }, - updateParams: ({params={}})=>{ + updateParams: ({ params = {} }) => { runMain(params) }, - init: (params)=>{ - let {baseURI, alias=[]} = params - if(!baseURI && typeof document != 'undefined' && document.baseURI){ + init: (params) => { + let { baseURI, alias = [] } = params + if (!baseURI && typeof document != 'undefined' && document.baseURI) { baseURI = document.baseURI } - if(baseURI) workerBaseURI = baseURI.toString() + if (baseURI) workerBaseURI = baseURI.toString() - alias.forEach(arr=>{ - let [orig, ...aliases] = arr - aliases.forEach(a=>{ + alias.forEach((arr) => { + const [orig, ...aliases] = arr + aliases.forEach((a) => { require.alias[a] = orig - if(a.toLowerCase().substr(-3)!=='.js') require.alias[a+'.js'] = orig + if (a.toLowerCase().substr(-3) !== '.js') + require.alias[a + '.js'] = orig }) }) initialized = true - if(onInit) onInit() + if (onInit) onInit() }, } @@ -414,23 +444,10 @@ const makeScriptWorker = ({callback, convertToSolids})=>{ } } - - - - - - - - - - - - - /** Make render worker */ -const makeRenderWorker = ()=>{ -let perspectiveCamera +const makeRenderWorker = () => { + let perspectiveCamera const state = {} const rotateSpeed = 0.002 @@ -444,10 +461,10 @@ let perspectiveCamera let entities = [] - function createContext (canvas, contextAttributes) { - function get (type) { + function createContext(canvas, contextAttributes) { + function get(type) { try { - return {gl:canvas.getContext(type, contextAttributes), type} + return { gl: canvas.getContext(type, contextAttributes), type } } catch (e) { return null } @@ -460,48 +477,59 @@ let perspectiveCamera ) } - const startRenderer = ({canvas, cameraPosition, cameraTarget, axis={}, grid={}})=>{ - const { prepareRender, drawCommands, cameras, controls } = require('@jscad/regl-renderer') + const startRenderer = ({ + canvas, + cameraPosition, + cameraTarget, + axis = {}, + grid = {}, + }) => { + const { + prepareRender, + drawCommands, + cameras, + controls, + } = require('@jscad/regl-renderer') perspectiveCamera = cameras.perspective orbitControls = controls.orbit state.canvas = canvas - state.camera = Object.assign({}, perspectiveCamera.defaults) - if(cameraPosition) state.camera.position = cameraPosition - if(cameraTarget) state.camera.target = cameraTarget + state.camera = { ...perspectiveCamera.defaults } + if (cameraPosition) state.camera.position = cameraPosition + if (cameraTarget) state.camera.target = cameraTarget - resize({ width:canvas.width, height:canvas.height }) + resize({ width: canvas.width, height: canvas.height }) state.controls = orbitControls.defaults - const {gl, type} = createContext(canvas) + const { gl, type } = createContext(canvas) // prepare the renderer const setupOptions = { - glOptions: {gl} + glOptions: { gl }, } - if(type == 'webgl'){ - setupOptions.glOptions.optionalExtensions = ['oes_element_index_uint'] + if (type == 'webgl') { + setupOptions.glOptions.optionalExtensions = ['oes_element_index_uint'] } renderer = prepareRender(setupOptions) gridOptions = { visuals: { drawCmd: 'drawGrid', - show: grid.show || grid.show === undefined , + show: grid.show || grid.show === undefined, color: grid.color || [0, 0, 0, 1], subColor: grid.subColor || [0, 0, 1, 0.5], fadeOut: false, - transparent: true + transparent: true, }, size: grid.size || [200, 200], - ticks: grid.ticks || [10, 1] + ticks: grid.ticks || [10, 1], } axisOptions = { visuals: { drawCmd: 'drawAxis', - show: axis.show || axis.show === undefined + show: axis.show || axis.show === undefined, }, size: axis.size || 100, } @@ -513,14 +541,10 @@ let perspectiveCamera drawAxis: drawCommands.drawAxis, drawGrid: drawCommands.drawGrid, drawLines: drawCommands.drawLines, - drawMesh: drawCommands.drawMesh + drawMesh: drawCommands.drawMesh, }, // define the visual content - entities: [ - gridOptions, - axisOptions, - ...entities - ] + entities: [gridOptions, axisOptions, ...entities], } // the heart of rendering, as themes, controls, etc change @@ -528,23 +552,31 @@ let perspectiveCamera } let renderTimer - const tmFunc = typeof requestAnimationFrame === 'undefined' ? setTimeout : requestAnimationFrame + const tmFunc = + typeof requestAnimationFrame === 'undefined' + ? setTimeout + : requestAnimationFrame - function updateView(delay=8){ - if(renderTimer || !renderer) return - renderTimer = tmFunc(updateAndRender,delay) + function updateView(delay = 8) { + if (renderTimer || !renderer) return + renderTimer = tmFunc(updateAndRender, delay) } const doRotatePanZoom = () => { - if (rotateDelta[0] || rotateDelta[1]) { - const updated = orbitControls.rotate({ controls: state.controls, camera: state.camera, speed: rotateSpeed }, rotateDelta) + const updated = orbitControls.rotate( + { controls: state.controls, camera: state.camera, speed: rotateSpeed }, + rotateDelta + ) state.controls = { ...state.controls, ...updated.controls } rotateDelta = [0, 0] } if (panDelta[0] || panDelta[1]) { - const updated = orbitControls.pan({ controls:state.controls, camera:state.camera, speed: panSpeed }, panDelta) + const updated = orbitControls.pan( + { controls: state.controls, camera: state.camera, speed: panSpeed }, + panDelta + ) state.controls = { ...state.controls, ...updated.controls } panDelta = [0, 0] state.camera.position = updated.camera.position @@ -552,7 +584,10 @@ let perspectiveCamera } if (zoomDelta) { - const updated = orbitControls.zoom({ controls:state.controls, camera:state.camera, speed: zoomSpeed }, zoomDelta) + const updated = orbitControls.zoom( + { controls: state.controls, camera: state.camera, speed: zoomSpeed }, + zoomDelta + ) state.controls = { ...state.controls, ...updated.controls } zoomDelta = 0 } @@ -562,55 +597,57 @@ let perspectiveCamera renderTimer = null doRotatePanZoom() - const updates = orbitControls.update({ controls: state.controls, camera: state.camera }) + const updates = orbitControls.update({ + controls: state.controls, + camera: state.camera, + }) state.controls = { ...state.controls, ...updates.controls } - if(state.controls.changed) updateView(16) // for elasticity in rotate / zoom + if (state.controls.changed) updateView(16) // for elasticity in rotate / zoom state.camera.position = updates.camera.position perspectiveCamera.update(state.camera) - renderOptions.entities = [ - gridOptions, - axisOptions, - ...entities - ] - let time = Date.now() + renderOptions.entities = [gridOptions, axisOptions, ...entities] + const time = Date.now() renderer(renderOptions) - if(updateRender){ - updateRender = ''; + if (updateRender) { + updateRender = '' } } - function resize({width,height}){ + function resize({ width, height }) { state.canvas.width = width state.canvas.height = height - perspectiveCamera.setProjection(state.camera, state.camera, { width, height }) + perspectiveCamera.setProjection(state.camera, state.camera, { + width, + height, + }) perspectiveCamera.update(state.camera, state.camera) updateView() } const handlers = { - pan: ({dx,dy})=>{ + pan: ({ dx, dy }) => { panDelta[0] += dx panDelta[1] += dy updateView() }, - rotate: ({dx,dy})=>{ + rotate: ({ dx, dy }) => { rotateDelta[0] -= dx rotateDelta[1] -= dy updateView() }, - zoom: ({dy})=>{ + zoom: ({ dy }) => { zoomDelta += dy updateView() }, resize, - entities: (params)=>{ + entities: (params) => { entities = params.entities updateRender = params.scriptStats updateView() }, - init: (params)=>{ - if(params.canvas) startRenderer(params) + init: (params) => { + if (params.canvas) startRenderer(params) initialized = true }, } @@ -621,42 +658,38 @@ let perspectiveCamera } } - function start(params) { - let { - callback=()=>{}, - convertToSolids=false - } = params + const { callback = () => {}, convertToSolids = false } = params // by default 'render' messages go outside of this instance (result of modeling) let scriptWorker - const sendCmd = (params, transfer)=>{ - if(params.worker === 'script') scriptWorker.postMessage(params, transfer) - else{ + const sendCmd = (params, transfer) => { + if (params.worker === 'script') scriptWorker.postMessage(params, transfer) + else { callback(params, transfer) } } - - - scriptWorker = makeScriptWorker({callback:sendCmd, convertToSolids}) - callback({action:'workerInit',worker:'main'}) + scriptWorker = makeScriptWorker({ callback: sendCmd, convertToSolids }) + callback({ action: 'workerInit', worker: 'main' }) return { - updateParams:({params={}})=>sendCmd({ action:'updateParams', worker:'script', params}), - runScript: ({script,url=''})=>sendCmd({ action:'runScript', worker:'script', script, url}), + updateParams: ({ params = {} }) => + sendCmd({ action: 'updateParams', worker: 'script', params }), + runScript: ({ script, url = '' }) => + sendCmd({ action: 'runScript', worker: 'script', script, url }), postMessage: sendCmd, } } const init = start({ convertToSolids: 'buffers', - callback:(params)=>self.postMessage(params), + callback: (params) => self.postMessage(params), }) -self.onmessage = ({data}) => { +self.onmessage = ({ data }) => { if (data.action === 'init') { workerBaseURI = data.baseURI } init.postMessage(data, null) -}; +}