format jscad worker

This commit is contained in:
Kurt Hutten
2021-09-08 06:18:11 +10:00
parent 22da074965
commit 58b618cf5f

View File

@@ -1,4 +1,3 @@
const setPoints = (points, p, i) => {
points[i++] = p[0]
points[i++] = p[1]
@@ -6,11 +5,12 @@ const setPoints = (points, p, i)=>{
}
function CSG2Vertices(csg) {
let idx = 0
const idx = 0
let vLen = 0, iLen = 0
for (let poly of csg.polygons){
let len = poly.vertices.length
let vLen = 0,
iLen = 0
for (const poly of csg.polygons) {
const len = poly.vertices.length
vLen += len * 3
iLen += 3 * (len - 2)
}
@@ -21,9 +21,9 @@ function CSG2Vertices(csg){
let indOffset = 0
let posOffset = 0
let first = 0
for (let poly of csg.polygons){
let arr = poly.vertices
let len = arr.length
for (const poly of csg.polygons) {
const arr = poly.vertices
const len = arr.length
first = posOffset
vertices.set(arr[0], vertOffset)
vertOffset += 3
@@ -44,13 +44,11 @@ function CSG2Vertices(csg){
return { vertices, indices, type: 'mesh' }
}
function CSG2LineVertices(csg) {
let vLen = csg.points.length * 3
if (csg.isClosed) vLen += 3
var vertices = new Float32Array(vLen)
const vertices = new Float32Array(vLen)
csg.points.forEach((p, idx) => setPoints(vertices, p, idx * 3))
@@ -61,16 +59,15 @@ function CSG2LineVertices(csg){
}
function CSG2LineSegmentsVertices(csg) {
let vLen = csg.sides.length * 6
const vLen = csg.sides.length * 6
var vertices = new Float32Array(vLen)
const vertices = new Float32Array(vLen)
csg.sides.forEach((side, idx) => {
let i = idx * 6
const i = idx * 6
setPoints(vertices, side[0], i)
setPoints(vertices, side[1], i + 3)
})
return { vertices, type: 'lines' }
}
function CSGCached(func, data, cacheKey, transferable) {
@@ -95,87 +92,100 @@ function CSGCached(func, data, cacheKey, 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
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
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;
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)
console.log(
'problem loading url ',
url,
'base',
workerBaseURI,
' error:',
e.message
)
throw e
}
}
function requireModule(url, source) {
try {
const exports={};
const exports = {}
if (!source) source = requireFile(url)
const module = { id: url, uri: url, exports:exports, source }; //according to node.js modules
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;
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
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;
console.error('Error loading module ' + url, err.message)
throw err
}
}
require.cache = {}
require.alias = {}
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())
let lines = script.split('\n').map((l) => l.trim())
lines = lines.map((l,i)=>{
lines = lines
.map((l, i) => {
return { code: l, line: i + 1, group: l[0] == '/' && !lines[i + 1] }
}).filter(l=>l.code)
})
.filter((l) => l.code)
let i = 0, line, next, lineNum
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;
if (line.length > 12 && line.substring(line.length - 13) == '//jscadparams')
break
if (line.length > 12 && line.indexOf('@jscad-params') != -1) break
}
let groupIndex = 1
@@ -190,7 +200,7 @@ function parseParams(script){
if (line[0] === '/') {
// group
const def = parseComment(line, lineNum)
let name = '_group_' +(groupIndex++)
let name = '_group_' + groupIndex++
let caption = def.caption
const idx = caption.lastIndexOf(':')
@@ -199,7 +209,6 @@ function parseParams(script){
caption = caption.substring(0, idx).trim()
}
defs.push({ name, type: 'group', caption, ...def.options })
} else {
const idx = line.indexOf('/')
if (idx === -1) {
@@ -207,11 +216,14 @@ function parseParams(script){
def.caption = def.name
defs.push(def)
} else {
defs.push(parseOne(
defs.push(
parseOne(
line.substring(idx).trim(),
line.substring(0, idx).trim(),
lineNum,lineNum
))
lineNum,
lineNum
)
)
}
}
i++
@@ -226,7 +238,8 @@ function parseOne(comment, code, line1, line2){
def.caption = def.caption || def.name
if (options) {
def = { ...def, ...options }
if(def.type === 'checkbox' && def.hasOwnProperty('initial')) def.checked = true
if (def.type === 'checkbox' && def.hasOwnProperty('initial'))
def.checked = true
if (def.type === 'slider') {
if (def.min === undefined) {
def.min = 0
@@ -237,7 +250,7 @@ function parseOne(comment, code, line1, line2){
}
}
return def;
return def
}
function parseComment(comment, line) {
@@ -253,8 +266,8 @@ function parseComment(comment, line){
try {
ret.options = eval('(' + comment.substring(idx) + ')')
} catch (e) {
console.log('Error in line '+line);
console.log(comment);
console.log('Error in line ' + line)
console.log(comment)
throw e
}
comment = comment.substring(0, idx).trim()
@@ -266,7 +279,8 @@ function parseComment(comment, line){
}
function parseDef(code, line) {
if(code[code.length-1] == ',') code = code.substring(0,code.length-1).trim()
if (code[code.length - 1] == ',')
code = code.substring(0, code.length - 1).trim()
let idx = code.indexOf('=')
if (idx == -1) idx = code.indexOf(':')
@@ -274,18 +288,16 @@ function parseDef(code, line){
if (idx == -1) {
return { name: code, type: 'text' }
} else {
let initial = code.substring(idx+1).trim()
const initial = code.substring(idx + 1).trim()
const ret = { type: 'text', name: code.substring(0, idx).trim() }
if (initial === 'true' || initial === 'false') {
ret.type = 'checkbox'
ret.checked = initial === 'true'
} else if (/^[0-9]+$/.test(initial)) {
ret.type = 'int'
ret.initial = parseFloat(initial)
} else if (/^[0-9]+\.[0-9]+$/.test(initial)) {
ret.type = 'number'
ret.initial = parseFloat(initial)
@@ -293,10 +305,10 @@ 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 = new EvalError(e.message, 'code', line)
e.lineNumber = line
throw e
}
@@ -306,40 +318,44 @@ function parseDef(code, line){
}
}
const makeScriptWorker = ({ callback, convertToSolids }) => {
let onInit, main, scriptStats, entities
function runMain(params = {}) {
let time = Date.now()
let solids
let transfer = []
const transfer = []
try {
let tmp = main(params)
const tmp = main(params)
solids = []
function flatten(arr) {
if (arr) {
if(arr instanceof Array)
arr.forEach(flatten)
else
solids.push(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)
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') {
CSGToBuffers.clearCache()
entities = solids.filter(s=>s).map((csg)=>{
let obj = CSGToBuffers(csg, transfer)
entities = solids
.filter((s) => s)
.map((csg) => {
const obj = CSGToBuffers(csg, transfer)
obj.color = csg.color
obj.transforms = csg.transforms
return obj
@@ -352,7 +368,10 @@ const makeScriptWorker = ({callback, convertToSolids})=>{
} else {
entities = solids
}
callback({action:'entities', worker:'render', entities, scriptStats}, transfer)
callback(
{ action: 'entities', worker: 'render', entities, scriptStats },
transfer
)
}
let initialized = false
@@ -365,15 +384,20 @@ const makeScriptWorker = ({callback, convertToSolids})=>{
try {
script_module = requireModule(url, script)
} catch (e) {
callback({action:'entities', worker:'render', error:e.message, stack:e.stack.toString()})
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) || []
const gp = script_module.exports.getParameterDefinitions
const paramsDef = parseParams(script) || []
if (gp) {
gp().forEach(p=>{
let idx = paramsDef.findIndex(old=>old.name == p.name)
gp().forEach((p) => {
const idx = paramsDef.findIndex((old) => old.name == p.name)
if (idx === -1) {
paramsDef.push(p)
} else {
@@ -381,7 +405,12 @@ const makeScriptWorker = ({callback, convertToSolids})=>{
}
})
}
if(paramsDef.length) callback({action:'parameterDefinitions', worker:'main', data:paramsDef})
if (paramsDef.length)
callback({
action: 'parameterDefinitions',
worker: 'main',
data: paramsDef,
})
runMain(params)
},
@@ -396,11 +425,12 @@ const makeScriptWorker = ({callback, convertToSolids})=>{
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
@@ -414,19 +444,6 @@ const makeScriptWorker = ({callback, convertToSolids})=>{
}
}
/** Make render worker */
const makeRenderWorker = () => {
@@ -460,14 +477,25 @@ 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)
state.camera = { ...perspectiveCamera.defaults }
if (cameraPosition) state.camera.position = cameraPosition
if (cameraTarget) state.camera.target = cameraTarget
@@ -478,7 +506,7 @@ let perspectiveCamera
const { gl, type } = createContext(canvas)
// prepare the renderer
const setupOptions = {
glOptions: {gl}
glOptions: { gl },
}
if (type == 'webgl') {
setupOptions.glOptions.optionalExtensions = ['oes_element_index_uint']
@@ -492,16 +520,16 @@ let perspectiveCamera
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,7 +552,10 @@ 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
@@ -536,15 +563,20 @@ let perspectiveCamera
}
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,28 +597,30 @@ 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
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 = '';
updateRender = ''
}
}
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()
}
@@ -621,12 +658,8 @@ 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
@@ -637,14 +670,14 @@ function start(params) {
}
}
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,
}
}
@@ -659,4 +692,4 @@ self.onmessage = ({data}) => {
workerBaseURI = data.baseURI
}
init.postMessage(data, null)
};
}