r3f-ify jsCadController

This commit is contained in:
Kurt Hutten
2021-08-21 11:04:01 +10:00
parent ac233a5920
commit b0647171d8
4 changed files with 65 additions and 45 deletions

View File

@@ -0,0 +1,183 @@
import {
RenderArgs,
DefaultKernelExport,
RenderResponse,
createUnhealthyResponse,
createHealthyResponse,
} from '../common'
import {
MeshPhongMaterial,
LineBasicMaterial,
BufferGeometry,
BufferAttribute,
Line,
LineSegments,
Color,
Mesh,
} from 'three'
import { jsCadToCadhubParams } from './jscadParams'
const materials = {
mesh: {
def: new MeshPhongMaterial({ color: 0x0084d1, flatShading: true }),
material: (params) => new MeshPhongMaterial(params),
},
line: {
def: new LineBasicMaterial({ color: 0x0000ff }),
material: ({ color, opacity, transparent }) =>
new LineBasicMaterial({ color, opacity, transparent }),
},
lines: null,
}
materials.lines = materials.line
interface CsgObj {
vertices: Float32Array
indices: Uint16Array
color?: [number, number, number, number]
transforms: number[]
type: 'mesh' | 'line' | 'lines'
}
function CSGArray2R3fComponent(Csgs: CsgObj[]): React.ReactNode {
return () =>
Csgs.map(({ vertices, indices, color, transforms, type }, index) => {
const materialDef = materials[type]
if (!materialDef) {
console.error('Can not hangle object type: ' + type, {
vertices,
indices,
color,
transforms,
type,
})
return null
}
let material = materialDef.def
if (color) {
const [r, g, b, opacity] = color
material = materialDef.material({
color: new Color(r, g, b),
flatShading: true,
opacity: opacity === void 0 ? 1 : opacity,
transparent: opacity != 1 && opacity !== void 0,
})
}
const geo = new BufferGeometry()
geo.setAttribute('position', new BufferAttribute(vertices, 3))
let mesh
switch (type) {
case 'mesh':
geo.setIndex(new BufferAttribute(indices, 1))
mesh = new Mesh(geo, material)
break
case 'line':
mesh = new Line(geo, material)
break
case 'lines':
mesh = new LineSegments(geo, material)
break
}
if (transforms) mesh.applyMatrix4({ elements: transforms })
return <primitive object={mesh} key={index} />
})
}
let scriptWorker
type ResolveFn = (RenderResponse) => void
class WorkerHelper {
callResolve: null | ResolveFn = null
previousCode = ''
resolver = (response: RenderResponse) => {
this.callResolve && this.callResolve(response)
this.callResolve = null
}
render = (
code: string,
parameters: { [key: string]: any }
): Promise<RenderResponse> => {
const response: Promise<RenderResponse> = new Promise(
(resolve: ResolveFn) => {
this.callResolve = resolve
}
)
if (!(code && this.previousCode !== code)) {
// we are not evaluating code, but reacting to parameters change
scriptWorker.postMessage({
action: 'updateParams',
worker: 'script',
params: parameters,
})
} else {
scriptWorker.postMessage({
action: 'runScript',
worker: 'script',
script: code,
params: parameters || {},
url: 'jscad_script',
})
}
this.previousCode = code
return response
}
}
const workerHelper = new WorkerHelper()
export const render: DefaultKernelExport['render'] = async ({
code,
parameters,
settings,
}: 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))
let parameterDefinitions = []
scriptWorker.addEventListener('message', ({ data }) => {
if (data.action == 'parameterDefinitions') {
parameterDefinitions = data.data
} else if (data.action == 'entities') {
if (data.error) {
workerHelper.resolver(createUnhealthyResponse(new Date(), data.error))
} else {
workerHelper.resolver(
createHealthyResponse({
type: 'r3f-component',
data: CSGArray2R3fComponent(data.entities),
consoleMessage: data.scriptStats,
date: new Date(),
customizerParams: jsCadToCadhubParams(parameterDefinitions || []),
})
)
}
}
})
workerHelper.resolver()
scriptWorker.postMessage({ action: 'init', baseURI, alias: [] })
}
return workerHelper.render(code, parameters)
}
const jsCadController: DefaultKernelExport = {
render,
}
export default jsCadController