Skip to content

Instantly share code, notes, and snippets.

@rattananen
Last active Jul 7, 2022
Embed
What would you like to do?
Babylonjs property missing in type ICanvasRenderingContext
import { Engine } from '@babylonjs/core/Engines/engine'
import { Scene } from '@babylonjs/core/scene'
import { AdvancedDynamicTexture } from '@babylonjs/gui/2D/advancedDynamicTexture'
import { Style } from '@babylonjs/gui/2D/style'
import { Slider, TextBlock, Control, Rectangle, Button } from '@babylonjs/gui'
import { Vector3, Vector4 } from '@babylonjs/core/Maths/math.vector'
import { HemisphericLight } from '@babylonjs/core/Lights/hemisphericLight'
import { ArcRotateCamera } from '@babylonjs/core/Cameras/arcRotateCamera'
import { Color3, Color4 } from '@babylonjs/core/Maths/math.color'
import { CSG } from '@babylonjs/core/Meshes'
import { Layer } from '@babylonjs/core/Layers/layer'
import { PBRMaterial } from '@babylonjs/core/Materials/PBR/pbrMaterial'
import { StandardMaterial } from '@babylonjs/core/Materials/standardMaterial'
import { Texture } from '@babylonjs/core/Materials/Textures/texture'
import { DynamicTexture } from '@babylonjs/core/Materials/Textures/dynamicTexture'
import { MeshBuilder } from '@babylonjs/core/Meshes/meshBuilder'
import { ArcRotateCameraMouseWheelInput } from '@babylonjs/core/Cameras/Inputs/arcRotateCameraMouseWheelInput'
import { collection as shapes, cutCollection as cutShapes } from 'src/helper/shape3d/collection'
import { ellipseCurve } from 'src/helper/shape3d'
import { hex2rgb } from 'src/helper/color'
import { isRect } from 'src/helper/util'
import { EquiRectangularCubeTexture } from '@babylonjs/core/Materials/Textures/equiRectangularCubeTexture'
import type { Mesh } from '@babylonjs/core/Meshes/mesh'
import type { LinesMesh } from '@babylonjs/core/Meshes/linesMesh'
import type { ProductHelper } from "src/helper/product"
const isLocalhost = window.location.hostname === 'localhost';
function hex2color3(hex: string) {
let rgb = hex2rgb(hex);
return new Color3(rgb.r, rgb.g, rgb.b)
}
function hex2color4(hex: string) {
let rgb = hex2rgb(hex);
return new Color4(rgb.r, rgb.g, rgb.b, 1)
}
function createHoleMesh(hole: SDN.ItemHole, rect: SDN.Rectangle, thick: number, scale: number, scene: Scene) {
let r = hole.r * scale
let cx = (rect.x + hole.cx) * scale
let cy = (rect.y + hole.cy) * scale
let shape = ellipseCurve(cx, cy, r, r, 0, Math.PI * 2, 40)
let len = 2 * Math.PI * hole.r
let faceUV = Array(3)
faceUV[1] = new Vector4(0, 0, len / 800, 50 / 450) //side
return MeshBuilder.ExtrudePolygon('cut', {
shape, depth: thick * scale, faceUV, wrap: true
}, scene);
}
const TEXTURE_LABEL_SIZE = 190;
const TEXTURE_LABEL_FONT = "bold 52px monospace";
const TEXTURE_LABEL_FONT_SIZE = 50;
function createLegendLabel(size: number, scene: Scene, name: string) {
let mesh = MeshBuilder.CreatePlane("plane", { size }, scene)
mesh.rotation.x = Math.PI;
let mat = new StandardMaterial("legendMat" + name, scene);
mat.backFaceCulling = false;
let texture = new DynamicTexture('legendTexture' + name, TEXTURE_LABEL_SIZE, scene, false);
texture.hasAlpha = true;
mat.diffuseTexture = texture;
mesh.material = mat
return mesh;
}
function setTextToLegend(mesh: Mesh, text: string, pos: Vector3) {
let material = mesh.material as StandardMaterial
let texture = material.diffuseTexture as DynamicTexture
let context = texture.getContext() as CanvasRenderingContext2D; //need to convert to CanvasRenderingContext2D in Babylonjs 5.0.3 to prevert property missing in type
context.clearRect(0, 0, TEXTURE_LABEL_SIZE, TEXTURE_LABEL_SIZE);
context.font = TEXTURE_LABEL_FONT;
context.textBaseline = 'top'; // property missing in type ICanvasRenderingContext in Babylonjs 5.0.3
let textW = context.measureText(text).width;
// context.fillStyle = '#FFF';
// context.fillRect(0,0, TEXTURE_LABEL_SIZE, TEXTURE_LABEL_SIZE);
context.fillStyle = '#008000';
context.fillText(text, (TEXTURE_LABEL_SIZE - textW) / 2, (TEXTURE_LABEL_SIZE - TEXTURE_LABEL_FONT_SIZE) / 2);
texture.update(false);
mesh.position = pos
}
function loadImg(url: string) {
return new Promise<HTMLImageElement>((resolve, reject) => {
let img = new Image();
if (isLocalhost) {
let newUrl = new URL(url);
newUrl.host = window.location.host;
img.src = newUrl.toString();
} else {
img.src = url;
}
img.onload = function () {
resolve(img)
}
img.onerror = function () {
reject('load image fail: ' + img.src)
}
})
}
type RendererData = {
shape: SDN.ItemShape | null,
shapeData: SDN.ShapeData | null,
holes: SDN.ItemHole[] | null,
cutouts: SDN.ItemCut[] | null,
product: ProductHelper | null,
edge: SDN.ItemEdge | null,
edges: SDN.Edge[] | null
}
type CreateSiderArgs = {
x: number,
y: number,
container: Rectangle,
min: number,
max: number,
style: Style,
defaultValue: number,
onChange: (v: number) => void,
valueFormator: (v: number) => string
}
type CreateBtnArgs = {
name: string,
x: number,
y: number,
text: string
container: Rectangle,
onClick: () => void,
}
export class Renderer {
protected data: RendererData
protected needUpdateMesh = false;
protected needUpdateLegend = false;
protected needUpdateMaterial = false;
protected needUpdateTexture = false;
protected needUpdateScene = false;
protected scale = 0.2
protected board?: Mesh
protected texture?: DynamicTexture
protected envTexture?: EquiRectangularCubeTexture
protected skybox?: Mesh
protected bgLayer?: Layer
protected legendLines?: LinesMesh
protected legendLabels: Mesh[] = []
protected legendNum = 0;
protected showLegend = true;
protected scene: Scene;
protected engine: Engine;
protected camera: ArcRotateCamera;
protected canvas: HTMLCanvasElement
protected material?: PBRMaterial
protected gui: AdvancedDynamicTexture
protected controls: Rectangle
constructor(canvas: HTMLCanvasElement) {
this.data = {
shape: null,
shapeData: null,
holes: null,
cutouts: null,
product: null,
edge: null,
edges: null
}
this.canvas = canvas
this.engine = new Engine(canvas, true);
this.scene = new Scene(this.engine);
this.camera = this.createCamera();
this.gui = AdvancedDynamicTexture.CreateFullscreenUI("ui", true);
this.controls = this.createControls()
this.onResize = this.onResize.bind(this);
}
run() {
const light = new HemisphericLight('light', new Vector3(0, 1, 0), this.scene);
light.groundColor = new Color3(0.8, 0.8, 0.8);
this.engine.runRenderLoop(() => {
this.scene.render();
})
}
createControls(): Rectangle {
const style = this.gui.createStyle();
style.fontFamily = "monospace"
style.fontSize = "12px"
const container = new Rectangle("panel");
container.width = "290px";
container.height = "170px";
container.background = "#666"
container.alpha = 0.6
container.zIndex = 1
const a = this.createSlider({
x: 5,
y: 5,
container,
style,
min: 0,
max: Math.PI * 2,
defaultValue: Math.PI / 2 * 3,
onChange: (value) => {
this.camera.alpha = value;
},
valueFormator: (value) => {
return "X: " + this.toDeg(value) + "°"
}
})
const b = this.createSlider({
x: 5,
y: 38,
container,
style,
min: 0,
max: Math.PI,
defaultValue: Math.PI/2,
onChange: (value) => {
this.camera.beta = value;
},
valueFormator: (value) => {
return "Y: " + this.toDeg(value) + "°"
}
})
const d = this.createSlider({
x: 5,
y: 71,
container,
style,
min: 15,
max: 250,
defaultValue: 30,
onChange: (value) => {
this.camera.radius = value;
},
valueFormator: (value) => {
return "Z: " + Math.round(value)
}
})
this.createBtn({
name: "btn-up",
x: 45,
y: 102,
container,
text: "🡅",
onClick: () => {
this.camera.inertialPanningY = -0.2
}
})
this.createBtn({
name: "btn-down",
x: 45,
y: 135,
container,
text: "🡇",
onClick: () => {
this.camera.inertialPanningY = 0.2
}
})
this.createBtn({
name: "btn-right",
x: 80,
y: 118,
container,
text: "🡆",
onClick: () => {
this.camera.inertialPanningX = -0.2
}
})
this.createBtn({
name: "btn-left",
x: 10,
y: 118,
container,
text: "🡄",
onClick: () => {
this.camera.inertialPanningX = 0.2
}
})
this.createBtn({
name: "btn-reset",
x: 200,
y: 118,
container,
text: "🗘",
onClick: () => {
a.value = Math.PI / 2 * 3;
b.value = Math.PI / 2;
d.value = 30;
this.camera.restoreState();
}
})
this.createBtn({
name: "btn-close",
x: 240,
y: 118,
container,
text: "✖",
onClick: () => {
this.setControlShow(false)
}
})
const btn = Button.CreateSimpleButton('btn-camera', '📷');
btn.paddingTopInPixels = 5
btn.paddingRightInPixels = 5
btn.width = "30px"
btn.height = "30px";
btn.color = "grey";
btn.background = "white"
btn.cornerRadius = 3
btn.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_RIGHT
btn.verticalAlignment = Control.VERTICAL_ALIGNMENT_TOP
btn.onPointerClickObservable.add(() => {
this.setControlShow(!this.isControlShow);
});
btn.zIndex = 0
this.gui.addControl(btn);
return container;
}
private _isControlShow = false
get isControlShow() {
return this._isControlShow;
}
setControlShow(b: boolean) {
if (this._isControlShow === b) {
return;
}
if (b) {
this.gui.addControl(this.controls);
this.setCamaraInputEnabled(false)
} else {
this.gui.removeControl(this.controls);
this.setCamaraInputEnabled(true)
}
this._isControlShow = b;
}
setCamaraInputEnabled(b: boolean) {
if (b) {
this.camera.inputs.attachElement(false);
} else {
this.camera.inputs.detachElement();
}
}
createSlider({ x, y, style, container, min, max, defaultValue, onChange, valueFormator }: CreateSiderArgs) {
const label = new TextBlock();
label.text = valueFormator(defaultValue);
label.resizeToFit = true;
label.color = "white";
label.leftInPixels = x
label.topInPixels = y
label.style = style;
label.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_LEFT
label.verticalAlignment = Control.VERTICAL_ALIGNMENT_TOP
container.addControl(label);
const input = new Slider();
input.minimum = min;
input.maximum = max;
input.value = defaultValue;
input.leftInPixels = x
input.topInPixels = y + 15
input.height = "16px";
input.width = "280px";
input.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_LEFT
input.verticalAlignment = Control.VERTICAL_ALIGNMENT_TOP
input.onValueChangedObservable.add((value) => {
label.text = valueFormator(value);
onChange(value)
});
container.addControl(input);
return input
}
createBtn({ name, x, y, text, container, onClick }: CreateBtnArgs): void {
const btn = Button.CreateSimpleButton(name, text);
btn.leftInPixels = x
btn.topInPixels = y
btn.width = "30px"
btn.height = "30px";
btn.color = "white";
btn.background = "black"
btn.onPointerClickObservable.add(onClick);
btn.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_LEFT
btn.verticalAlignment = Control.VERTICAL_ALIGNMENT_TOP
container.addControl(btn);
}
toDeg(radian: number): number {
return Math.round(radian * 180 / Math.PI)
}
createCamera(): ArcRotateCamera {
const camera = new ArcRotateCamera('camera', Math.PI / 2 * 3, Math.PI / 2, 30, new Vector3(0, 0, 0), this.scene);
camera.storeState();
camera.attachControl(this.canvas, false);
const mousewheel = camera.inputs.attached.mousewheel as ArcRotateCameraMouseWheelInput;
mousewheel.wheelPrecision = 0;
mousewheel.wheelDeltaPercentage = 0.05
camera.inputs.remove(camera.inputs.attached.mousewheel);
this.canvas.addEventListener('click', () => {
this.camera.inputs.add(mousewheel);
}, { once: true })
return camera
}
attachEvent() {
window.addEventListener('resize', this.onResize);
}
detachEvent() {
window.removeEventListener('resize', this.onResize);
}
onResize() {
this.engine.resize();
}
updateData(product: ProductHelper, shape: SDN.ItemShape, shapeData: SDN.ShapeData, holes: SDN.ItemHole[], cutouts: SDN.ItemCut[], edge: SDN.ItemEdge | null, edges: SDN.Edge[] | null) {
if (this.data.product == null) {
this.needUpdateScene = true;
this.needUpdateMaterial = true;
this.needUpdateTexture = true;
this.needUpdateMesh = true;
this.needUpdateLegend = true;
} else {
if (this.data.product.getDefaultScene().id !== product.getDefaultScene().id) {
this.needUpdateScene = true;
}
if (this.data.product.getMaterial3d().id !== product.getMaterial3d().id) {
this.needUpdateMaterial = true;
this.needUpdateTexture = true;
}
}
if (this.data.shape !== shape
|| this.data.shapeData !== shapeData
|| this.data.holes !== holes
|| this.data.cutouts !== cutouts) {
this.needUpdateMesh = true;
}
if (this.data.shape !== shape
|| this.data.shapeData !== shapeData) {
this.needUpdateLegend = true;
}
if (this.data.edge != null && this.data.shapeData !== shapeData) {
this.needUpdateTexture = true;
}
if (this.data.edge != null && this.data.shape !== shape) {
this.needUpdateTexture = true;
}
if (this.data.edge !== edge && isRect(shape.shid)) {
this.needUpdateTexture = true;
}
this.data.product = product;
this.data.shape = shape;
this.data.shapeData = shapeData;
this.data.holes = holes;
this.data.cutouts = cutouts;
this.data.edge = edge;
this.data.edges = edges;
this.tryUpdate();
}
tryUpdate() {
let resetCamera = this.needUpdateMesh || this.needUpdateMaterial;
if (this.needUpdateMesh) {
this.updateMesh();
this.needUpdateMesh = false;
}
if (this.needUpdateMaterial) {
this.updateMaterial();
this.needUpdateMaterial = false;
}
if (this.needUpdateTexture) {
this.updateTexture();
this.needUpdateTexture = false;
}
if (this.needUpdateLegend) {
this.updateLegend();
this.needUpdateLegend = false;
}
if (this.needUpdateScene) {
this.updateScene();
this.needUpdateScene = false;
}
if (resetCamera) {
this.camera.restoreState();
}
}
updateMesh() {
if (this.data.shape === null ||
this.data.shapeData === null ||
this.data.holes === null ||
this.data.cutouts === null ||
this.data.product === null) {
throw new Error("miss data in updateMesh")
}
let shape = shapes.find(item => item.id === this.data.shape?.shid);
if (typeof shape === 'undefined') {
throw new Error("shape not found in updateMesh")
}
let shapeRect = shape.getRect(this.data.shapeData!);
let thick = this.data.product.getThick() / 10;
if (typeof this.board !== 'undefined') {
this.board.dispose()
}
this.board = shape.createMesh(this.data.shapeData, thick, this.scale, this.scene)
if (this.data.holes.length > 0 || this.data.cutouts.length > 0) {
let aCSG = CSG.FromMesh(this.board);
let cutMesh, cCSG;
for (let i = 0, j = this.data.holes.length; i < j; i++) {
cutMesh = createHoleMesh(this.data.holes[i], shapeRect, thick, this.scale, this.scene)
cCSG = CSG.FromMesh(cutMesh);
aCSG.subtractInPlace(cCSG);
cutMesh.dispose();
}
let cutShape;
for (let i = 0, j = this.data.cutouts.length; i < j; i++) {
cutShape = cutShapes.find(item => item.id == this.data.cutouts![i].cutId);
if (typeof cutShape === 'undefined') {
throw new Error("cutShape not found in updateMesh")
}
cutMesh = cutShape.createCutMesh(this.data.cutouts[i].data, shapeRect, thick, this.scale, this.scene)
cCSG = CSG.FromMesh(cutMesh);
aCSG.subtractInPlace(cCSG);
cutMesh.dispose();
}
this.board.dispose()
this.board = aCSG.toMesh("board", null, this.scene);
}
this.board.rotation.x = -Math.PI / 2
if (!this.needUpdateMaterial && typeof this.material !== 'undefined') {
this.board.material = this.material;
}
}
updateMaterial() {
if (typeof this.material !== 'undefined') {
this.material.dispose()
}
if (this.data.product === null ||
this.board === null) {
throw new Error("miss data in updateMaterial")
}
let matData = this.data.product.getMaterial3d();
this.material = new PBRMaterial('mat', this.scene);
this.material.alpha = matData.alpha
this.material.useAlphaFromAlbedoTexture = matData.useTextureAlpha
this.material.backFaceCulling = (matData.alpha == 1) || matData.useTextureAlpha;
this.material.albedoColor = hex2color3(matData.diffuseColor)
//this.material.diffuseColor = hex2color3(matData.diffuseColor)
this.material.emissiveColor = hex2color3(matData.emissiveColor)
this.material.metallic = matData.metallic
this.material.metallicF0Factor = 0
this.material.roughness = matData.roughness
this.material.subSurface.isRefractionEnabled = matData.useRefraction
this.material.subSurface.refractionIntensity = matData.refractionIntensity
if (typeof this.board !== 'undefined') {
this.board.material = this.material;
}
}
updateTexture() {
if (this.data.product === null ||
typeof this.material === 'undefined') {
throw new Error("miss data in updateTexture")
}
let matData = this.data.product.getMaterial3d();
if (!this.texture) {
this.texture = new DynamicTexture('texture', { width: 800, height: 450 }, this.scene, false);
}
//this.texture.hasAlpha = matData.useTextureAlpha;
let context = this.texture.getContext();
context.clearRect(0, 0, 800, 450);
let stack = [], loader;
if (matData.textureFront !== null) {
loader = loadImg(matData.textureFront.url).then((img) => {
context.drawImage(img, 0, 0, img.width, img.height, 0, 0, 400, 400);
})
stack.push(loader);
}
if (matData.textureBack !== null) {
loader = loadImg(matData.textureBack.url).then((img) => {
context.drawImage(img, 0, 0, img.width, img.height, 400, 0, 400, 400);
})
stack.push(loader);
}
if (matData.textureSide !== null) {
loader = loadImg(matData.textureSide.url).then((img) => {
context.drawImage(img, 0, 0, img.width, img.height, 0, 400, 800, 50);
})
stack.push(loader);
}
if (stack.length > 0) {
Promise.all(stack).then(() => {
if (this.data.edge !== null && isRect(this.data.shape!.shid)) {
this.updateEdge();
} else {
this.texture!.update()
}
})
this.material.albedoTexture = this.texture
} else {
//@ts-ignore
this.material.albedoTexture = null//actually nullable
}
}
updateEdge() {
if (typeof this.texture === 'undefined' ||
this.data.edges === null ||
this.data.shapeData === null ||
this.data.edge === null) {
throw new Error("miss data in updateEdge")
}
let context = this.texture.getContext();
let edge = this.data.edges.find(item => item.id == this.data.edge?.edid)
if (typeof edge === 'undefined') {
throw new Error("edge not found in updateEdge")
}
let factor = 0.5;
let w = this.data.shapeData.w * factor;
let h = this.data.shapeData.h * factor;
let cursor = 0;
let replaceAll = this.data.edge.top && this.data.edge.left && this.data.edge.bottom && this.data.edge.right;
loadImg(edge.texture.url).then((img) => {
if (replaceAll) {
context.drawImage(img, 0, 0, img.width, img.height, 0, 400, 800, 50);
} else {
let r = img.width / 800;
if (this.data.edge!.top) {
context.drawImage(img, cursor * r, 0, w * r, img.height, cursor, 400, w, 50);
}
cursor += w;
if (this.data.edge!.left) {
context.drawImage(img, cursor * r, 0, h * r, img.height, cursor, 400, h, 50);
}
cursor += h;
if (this.data.edge!.bottom) {
context.drawImage(img, cursor * r, 0, w * r, img.height, cursor, 400, w, 50);
}
cursor += w;
if (this.data.edge!.right) {
context.drawImage(img, cursor * r, 0, h * r, img.height, cursor, 400, h, 50);
}
}
this.texture!.update();
})
}
updateLegend() {
let shape = shapes.find(item => item.id == this.data.shape!.shid);
if (typeof shape === 'undefined') {
throw new Error("shape was not found: " + this.data.shape!.shid);
}
let legend = shape.getLegendPoints(this.data.shapeData!, this.scale, 2);
this.legendNum = legend.label.length;
if (this.legendLines) {
this.legendLines.dispose();
}
this.legendLines = MeshBuilder.CreateLineSystem("legendLines", { lines: legend.line }, this.scene);
this.legendLines.color = new Color3(0, 0.5019607843137255, 0);
for (let i = 0; i < this.legendNum; i++) {
if (typeof this.legendLabels[i] === 'undefined') {
this.legendLabels[i] = createLegendLabel(15 * this.scale, this.scene, i.toString());
}
setTextToLegend(this.legendLabels[i], legend.label[i].size + "cm", legend.label[i].pos);
}
this.setVisibleToLegend(this.showLegend);
}
setVisibleToLegend(isVisible: boolean) {
this.legendLabels.forEach((item, index) => {
if (index < this.legendNum) {
item.isVisible = isVisible;
} else {
item.isVisible = false
}
})
this.legendLines!.isVisible = isVisible;
}
updateScene() {
if (this.data.product === null) {
throw new Error("miss data in product")
}
let sceneData = this.data.product.getDefaultScene();
this.scene.clearColor = hex2color4(sceneData.clearColor)
if (this.envTexture) {
this.scene.environmentTexture = null
this.envTexture.dispose();
}
if (this.skybox && this.skybox.material) {
this.skybox.material.dispose();
this.skybox.dispose();
delete this.skybox
}
if (sceneData.envFile != null) {
this.envTexture = new EquiRectangularCubeTexture(sceneData.envFile.url, this.scene, 512)
this.scene.environmentTexture = this.envTexture
if (sceneData.useSkybox) {
this.skybox = MeshBuilder.CreateBox('skybox', { size: 1000 }, this.scene)
let skyboxMaterial = new StandardMaterial("skyBox", this.scene);
skyboxMaterial.backFaceCulling = false;
skyboxMaterial.disableLighting = true;
skyboxMaterial.reflectionTexture = this.envTexture.clone()
skyboxMaterial.reflectionTexture.coordinatesMode = Texture.SKYBOX_MODE;
this.skybox.material = skyboxMaterial;
this.skybox.infiniteDistance = true;
}
}
if (this.bgLayer) {
this.bgLayer.dispose()
}
if (sceneData.bgFile != null && !sceneData.useSkybox) {
this.bgLayer = new Layer('bg', sceneData.bgFile.url, this.scene, true)
}
}
dispose() {
this.data.product = null;
this.data.shape = null;
this.data.shapeData = null;
this.data.holes = null;
this.data.cutouts = null;
this.data.edge = null;
this.data.edges = null;
delete this.board
delete this.texture
delete this.envTexture
delete this.bgLayer
delete this.legendLines;
delete this.skybox;
this.scene.dispose();
}
}
import React from 'react'
import { Renderer } from './renderer'
import { useThreeDData, useCameraPoints } from 'src/state/hook'
export default () => {
// let { canvasRef, renderer } = useRenderer();
const canvasRef = React.useRef<HTMLCanvasElement>(null);
const renderRef = React.useRef<Renderer>();
let { product, shape, holes, cutouts, edge, edges } = useThreeDData();
let { cameraPoints } = useCameraPoints()
React.useEffect(() => {
if (canvasRef.current === null) {
return
}
const r = new Renderer(canvasRef.current)
r.run();
r.attachEvent();
renderRef.current = r
return () => {
r.detachEvent();
r.dispose();
}
}, [canvasRef])
React.useEffect(() => {
if (typeof renderRef.current === 'undefined' || product === null) {
return;
}
const time = setTimeout((renderer, product, shape, shapeData, holes, cutouts, edge, edges) => {
renderer.updateData(product, shape, shapeData, holes, cutouts, edge, edges);
}, 800, renderRef.current, product, shape, shape.data, holes, cutouts, edge, edges)
return () => {
clearTimeout(time)
}
}, [product, shape.data, holes, cutouts, edge]);
React.useEffect(() => {
if (typeof renderRef.current === 'undefined' || product === null) {
return;
}
renderRef.current.updateCameraFocus(cameraPoints)
}, [cameraPoints]);
return <div className="sd-view3d">
<div className="sd-view3d__scene">
<canvas ref={canvasRef} />
</div>
</div>
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment