UNPKG

@threepipe/plugin-svg-renderer

Version:

Plugins for SVG Rendering of 3d objects for Threepipe

1,319 lines 1.12 MB
/** * @license * @threepipe/plugin-svg-renderer v0.2.5 * Copyright 2022-2025 repalash <palash@shaders.app> * GPLV3 License * See ./dependencies.txt for any bundled third-party dependencies and licenses. */ import { Vector3, Vector4, Box3, Matrix4, Frustum, Vector2, Color as Color$1, Matrix3, DoubleSide, Box2, SRGBColorSpace, Camera, uiToggle, uiConfig, uiButton, uiFolderContainer, AViewerPluginSync, computeMorphedAttributes, Triangle, BufferAttribute, Plane, Line3 as Line3$1, Sphere, BackSide, FrontSide, Ray, Mesh, onChange, uiVector, timeout } from "threepipe"; import { Vector3 as Vector3$1, Matrix4 as Matrix4$1, Vector2 as Vector2$1, Color as Color$2, Raycaster, DoubleSide as DoubleSide$1, Line3, PerspectiveCamera, Box2 as Box2$1, Triangle as Triangle$1, Mesh as Mesh$1 } from "threepipe"; class RenderableObject { constructor() { this.id = 0; this.object = null; this.z = 0; this.renderOrder = 0; } } class RenderableFace { constructor() { this.id = 0; this.v1 = new RenderableVertex(); this.v2 = new RenderableVertex(); this.v3 = new RenderableVertex(); this.normalModel = new Vector3(); this.vertexNormalsModel = [new Vector3(), new Vector3(), new Vector3()]; this.vertexNormalsLength = 0; this.color = new Color$1(); this.material = null; this.uvs = [new Vector2(), new Vector2(), new Vector2()]; this.z = 0; this.renderOrder = 0; } } class RenderableVertex { constructor() { this.position = new Vector3(); this.positionWorld = new Vector3(); this.positionScreen = new Vector4(); this.visible = true; } copy(vertex) { this.positionWorld.copy(vertex.positionWorld); this.positionScreen.copy(vertex.positionScreen); } } class RenderableLine { constructor() { this.id = 0; this.v1 = new RenderableVertex(); this.v2 = new RenderableVertex(); this.vertexColors = [new Color$1(), new Color$1()]; this.material = null; this.z = 0; this.renderOrder = 0; } } class RenderableSprite { constructor() { this.id = 0; this.object = null; this.x = 0; this.y = 0; this.z = 0; this.rotation = 0; this.scale = new Vector2(); this.material = null; this.renderOrder = 0; } } class Projector { constructor() { let _object, _objectCount, _objectPoolLength = 0, _vertex, _vertexCount, _vertexPoolLength = 0, _face, _faceCount, _facePoolLength = 0, _line2, _lineCount, _linePoolLength = 0, _sprite, _spriteCount, _spritePoolLength = 0, _modelMatrix; const _renderData = { objects: [], lights: [], elements: [] }, _vector3 = new Vector3(), _vector4 = new Vector4(), _clipBox = new Box3(new Vector3(-1, -1, -1), new Vector3(1, 1, 1)), _boundingBox = new Box3(), _points3 = new Array(3), _viewMatrix = new Matrix4(), _viewProjectionMatrix = new Matrix4(), _modelViewProjectionMatrix = new Matrix4(), _frustum = new Frustum(), _objectPool = [], _vertexPool = [], _facePool = [], _linePool = [], _spritePool = []; function RenderList() { const normals = []; const colors = []; const uvs = []; let object = null; const normalMatrix = new Matrix3(); function setObject(value) { object = value; normalMatrix.getNormalMatrix(object.matrixWorld); normals.length = 0; colors.length = 0; uvs.length = 0; } function projectVertex(vertex) { const position2 = vertex.position; const positionWorld = vertex.positionWorld; const positionScreen = vertex.positionScreen; positionWorld.copy(position2).applyMatrix4(_modelMatrix); positionScreen.copy(positionWorld).applyMatrix4(_viewProjectionMatrix); const invW = 1 / positionScreen.w; positionScreen.x *= invW; positionScreen.y *= invW; positionScreen.z *= invW; vertex.visible = positionScreen.x >= -1 && positionScreen.x <= 1 && positionScreen.y >= -1 && positionScreen.y <= 1 && positionScreen.z >= -1 && positionScreen.z <= 1; } function pushVertex(x2, y2, z) { _vertex = getNextVertexInPool(); _vertex.position.set(x2, y2, z); projectVertex(_vertex); } function pushNormal(x2, y2, z) { normals.push(x2, y2, z); } function pushColor(r, g, b2) { colors.push(r, g, b2); } function pushUv(x2, y2) { uvs.push(x2, y2); } function checkTriangleVisibility(v1, v2, v3) { if (v1.visible === true || v2.visible === true || v3.visible === true) return true; _points3[0] = v1.positionScreen; _points3[1] = v2.positionScreen; _points3[2] = v3.positionScreen; return _clipBox.intersectsBox(_boundingBox.setFromPoints(_points3)); } function checkBackfaceCulling(v1, v2, v3) { return (v3.positionScreen.x - v1.positionScreen.x) * (v2.positionScreen.y - v1.positionScreen.y) - (v3.positionScreen.y - v1.positionScreen.y) * (v2.positionScreen.x - v1.positionScreen.x) < 0; } function pushLine(a, b2) { const v1 = _vertexPool[a]; const v2 = _vertexPool[b2]; v1.positionScreen.copy(v1.position).applyMatrix4(_modelViewProjectionMatrix); v2.positionScreen.copy(v2.position).applyMatrix4(_modelViewProjectionMatrix); if (clipLine(v1.positionScreen, v2.positionScreen) === true) { v1.positionScreen.multiplyScalar(1 / v1.positionScreen.w); v2.positionScreen.multiplyScalar(1 / v2.positionScreen.w); _line2 = getNextLineInPool(); _line2.id = object.id; _line2.v1.copy(v1); _line2.v2.copy(v2); _line2.z = Math.max(v1.positionScreen.z, v2.positionScreen.z); _line2.renderOrder = object.renderOrder; _line2.material = object.material; if (object.material.vertexColors) { _line2.vertexColors[0].fromArray(colors, a * 3); _line2.vertexColors[1].fromArray(colors, b2 * 3); } _renderData.elements.push(_line2); } } function pushTriangle(a, b2, c2, material) { const v1 = _vertexPool[a]; const v2 = _vertexPool[b2]; const v3 = _vertexPool[c2]; if (checkTriangleVisibility(v1, v2, v3) === false) return; if (material.side === DoubleSide || checkBackfaceCulling(v1, v2, v3) === true) { _face = getNextFaceInPool(); _face.id = object.id; _face.v1.copy(v1); _face.v2.copy(v2); _face.v3.copy(v3); _face.z = (v1.positionScreen.z + v2.positionScreen.z + v3.positionScreen.z) / 3; _face.renderOrder = object.renderOrder; _vector3.subVectors(v3.position, v2.position); _vector4.subVectors(v1.position, v2.position); _vector3.cross(_vector4); _face.normalModel.copy(_vector3); _face.normalModel.applyMatrix3(normalMatrix).normalize(); for (let i = 0; i < 3; i++) { const normal = _face.vertexNormalsModel[i]; normal.fromArray(normals, arguments[i] * 3); normal.applyMatrix3(normalMatrix).normalize(); const uv = _face.uvs[i]; uv.fromArray(uvs, arguments[i] * 2); } _face.vertexNormalsLength = 3; _face.material = material; if (material.vertexColors) { _face.color.fromArray(colors, a * 3); } _renderData.elements.push(_face); } } return { setObject, projectVertex, checkTriangleVisibility, checkBackfaceCulling, pushVertex, pushNormal, pushColor, pushUv, pushLine, pushTriangle }; } const renderList = new RenderList(); function projectObject(object) { if (object.visible === false) return; if (object.isLight) { _renderData.lights.push(object); } else if (object.isMesh || object.isLine || object.isPoints) { if (object.material.visible === false) return; if (object.frustumCulled === true && _frustum.intersectsObject(object) === false) return; addObject(object); } else if (object.isSprite) { if (object.material.visible === false) return; if (object.frustumCulled === true && _frustum.intersectsSprite(object) === false) return; addObject(object); } const children = object.children; for (let i = 0, l = children.length; i < l; i++) { projectObject(children[i]); } } function addObject(object) { _object = getNextObjectInPool(); _object.id = object.id; _object.object = object; _vector3.setFromMatrixPosition(object.matrixWorld); _vector3.applyMatrix4(_viewProjectionMatrix); _object.z = _vector3.z; _object.renderOrder = object.renderOrder; _renderData.objects.push(_object); } this.projectScene = function(scene, camera, sortObjects, sortElements) { _faceCount = 0; _lineCount = 0; _spriteCount = 0; _renderData.elements.length = 0; if (scene.matrixWorldAutoUpdate === true) scene.updateMatrixWorld(); if (camera.parent === null && camera.matrixWorldAutoUpdate === true) camera.updateMatrixWorld(); _viewMatrix.copy(camera.matrixWorldInverse); _viewProjectionMatrix.multiplyMatrices(camera.projectionMatrix, _viewMatrix); _frustum.setFromProjectionMatrix(_viewProjectionMatrix); _objectCount = 0; _renderData.objects.length = 0; _renderData.lights.length = 0; projectObject(scene); if (sortObjects === true) { _renderData.objects.sort(painterSort); } const objects = _renderData.objects; for (let o = 0, ol = objects.length; o < ol; o++) { const object = objects[o].object; const geometry = object.geometry; renderList.setObject(object); _modelMatrix = object.matrixWorld; _vertexCount = 0; if (object.isMesh) { let material = object.material; const isMultiMaterial = Array.isArray(material); const attributes = geometry.attributes; const groups = geometry.groups; if (attributes.position === void 0) continue; const positions = attributes.position.array; for (let i = 0, l = positions.length; i < l; i += 3) { let x2 = positions[i]; let y2 = positions[i + 1]; let z = positions[i + 2]; const morphTargets = geometry.morphAttributes.position; if (morphTargets !== void 0) { const morphTargetsRelative = geometry.morphTargetsRelative; const morphInfluences = object.morphTargetInfluences; for (let t = 0, tl = morphTargets.length; t < tl; t++) { const influence = morphInfluences[t]; if (influence === 0) continue; const target = morphTargets[t]; if (morphTargetsRelative) { x2 += target.getX(i / 3) * influence; y2 += target.getY(i / 3) * influence; z += target.getZ(i / 3) * influence; } else { x2 += (target.getX(i / 3) - positions[i]) * influence; y2 += (target.getY(i / 3) - positions[i + 1]) * influence; z += (target.getZ(i / 3) - positions[i + 2]) * influence; } } } renderList.pushVertex(x2, y2, z); } if (attributes.normal !== void 0) { const normals = attributes.normal.array; for (let i = 0, l = normals.length; i < l; i += 3) { renderList.pushNormal(normals[i], normals[i + 1], normals[i + 2]); } } if (attributes.color !== void 0) { const colors = attributes.color.array; for (let i = 0, l = colors.length; i < l; i += 3) { renderList.pushColor(colors[i], colors[i + 1], colors[i + 2]); } } if (attributes.uv !== void 0) { const uvs = attributes.uv.array; for (let i = 0, l = uvs.length; i < l; i += 2) { renderList.pushUv(uvs[i], uvs[i + 1]); } } if (geometry.index !== null) { const indices = geometry.index.array; if (groups.length > 0) { for (let g = 0; g < groups.length; g++) { const group = groups[g]; material = isMultiMaterial === true ? object.material[group.materialIndex] : object.material; if (material === void 0) continue; for (let i = group.start, l = group.start + group.count; i < l; i += 3) { renderList.pushTriangle(indices[i], indices[i + 1], indices[i + 2], material); } } } else { for (let i = 0, l = indices.length; i < l; i += 3) { renderList.pushTriangle(indices[i], indices[i + 1], indices[i + 2], material); } } } else { if (groups.length > 0) { for (let g = 0; g < groups.length; g++) { const group = groups[g]; material = isMultiMaterial === true ? object.material[group.materialIndex] : object.material; if (material === void 0) continue; for (let i = group.start, l = group.start + group.count; i < l; i += 3) { renderList.pushTriangle(i, i + 1, i + 2, material); } } } else { for (let i = 0, l = positions.length / 3; i < l; i += 3) { renderList.pushTriangle(i, i + 1, i + 2, material); } } } } else if (object.isLine) { _modelViewProjectionMatrix.multiplyMatrices(_viewProjectionMatrix, _modelMatrix); const attributes = geometry.attributes; if (attributes.position !== void 0) { const positions = attributes.position.array; for (let i = 0, l = positions.length; i < l; i += 3) { renderList.pushVertex(positions[i], positions[i + 1], positions[i + 2]); } if (attributes.color !== void 0) { const colors = attributes.color.array; for (let i = 0, l = colors.length; i < l; i += 3) { renderList.pushColor(colors[i], colors[i + 1], colors[i + 2]); } } if (geometry.index !== null) { const indices = geometry.index.array; for (let i = 0, l = indices.length; i < l; i += 2) { renderList.pushLine(indices[i], indices[i + 1]); } } else { const step = object.isLineSegments ? 2 : 1; for (let i = 0, l = positions.length / 3 - 1; i < l; i += step) { renderList.pushLine(i, i + 1); } } } } else if (object.isPoints) { _modelViewProjectionMatrix.multiplyMatrices(_viewProjectionMatrix, _modelMatrix); const attributes = geometry.attributes; if (attributes.position !== void 0) { const positions = attributes.position.array; for (let i = 0, l = positions.length; i < l; i += 3) { _vector4.set(positions[i], positions[i + 1], positions[i + 2], 1); _vector4.applyMatrix4(_modelViewProjectionMatrix); pushPoint(_vector4, object, camera); } } } else if (object.isSprite) { object.modelViewMatrix.multiplyMatrices(camera.matrixWorldInverse, object.matrixWorld); _vector4.set(_modelMatrix.elements[12], _modelMatrix.elements[13], _modelMatrix.elements[14], 1); _vector4.applyMatrix4(_viewProjectionMatrix); pushPoint(_vector4, object, camera); } } if (sortElements === true) { _renderData.elements.sort(painterSort); } return _renderData; }; function pushPoint(_vector42, object, camera) { const invW = 1 / _vector42.w; _vector42.z *= invW; if (_vector42.z >= -1 && _vector42.z <= 1) { _sprite = getNextSpriteInPool(); _sprite.id = object.id; _sprite.x = _vector42.x * invW; _sprite.y = _vector42.y * invW; _sprite.z = _vector42.z; _sprite.renderOrder = object.renderOrder; _sprite.object = object; _sprite.rotation = object.rotation; _sprite.scale.x = object.scale.x * Math.abs(_sprite.x - (_vector42.x + camera.projectionMatrix.elements[0]) / (_vector42.w + camera.projectionMatrix.elements[12])); _sprite.scale.y = object.scale.y * Math.abs(_sprite.y - (_vector42.y + camera.projectionMatrix.elements[5]) / (_vector42.w + camera.projectionMatrix.elements[13])); _sprite.material = object.material; _renderData.elements.push(_sprite); } } function getNextObjectInPool() { if (_objectCount === _objectPoolLength) { const object = new RenderableObject(); _objectPool.push(object); _objectPoolLength++; _objectCount++; return object; } return _objectPool[_objectCount++]; } function getNextVertexInPool() { if (_vertexCount === _vertexPoolLength) { const vertex = new RenderableVertex(); _vertexPool.push(vertex); _vertexPoolLength++; _vertexCount++; return vertex; } return _vertexPool[_vertexCount++]; } function getNextFaceInPool() { if (_faceCount === _facePoolLength) { const face = new RenderableFace(); _facePool.push(face); _facePoolLength++; _faceCount++; return face; } return _facePool[_faceCount++]; } function getNextLineInPool() { if (_lineCount === _linePoolLength) { const line = new RenderableLine(); _linePool.push(line); _linePoolLength++; _lineCount++; return line; } return _linePool[_lineCount++]; } function getNextSpriteInPool() { if (_spriteCount === _spritePoolLength) { const sprite = new RenderableSprite(); _spritePool.push(sprite); _spritePoolLength++; _spriteCount++; return sprite; } return _spritePool[_spriteCount++]; } function painterSort(a, b2) { if (a.renderOrder !== b2.renderOrder) { return a.renderOrder - b2.renderOrder; } else if (a.z !== b2.z) { return b2.z - a.z; } else if (a.id !== b2.id) { return a.id - b2.id; } else { return 0; } } function clipLine(s1, s2) { let alpha1 = 0, alpha2 = 1; const bc1near = s1.z + s1.w, bc2near = s2.z + s2.w, bc1far = -s1.z + s1.w, bc2far = -s2.z + s2.w; if (bc1near >= 0 && bc2near >= 0 && bc1far >= 0 && bc2far >= 0) { return true; } else if (bc1near < 0 && bc2near < 0 || bc1far < 0 && bc2far < 0) { return false; } else { if (bc1near < 0) { alpha1 = Math.max(alpha1, bc1near / (bc1near - bc2near)); } else if (bc2near < 0) { alpha2 = Math.min(alpha2, bc1near / (bc1near - bc2near)); } if (bc1far < 0) { alpha1 = Math.max(alpha1, bc1far / (bc1far - bc2far)); } else if (bc2far < 0) { alpha2 = Math.min(alpha2, bc1far / (bc1far - bc2far)); } if (alpha2 < alpha1) { return false; } else { s1.lerp(s2, alpha1); s2.lerp(s1, 1 - alpha2); return true; } } } } } let SVGRenderer$1 = class SVGRenderer { constructor() { let _renderData, _elements, _lights, _svgWidth, _svgHeight, _svgWidthHalf, _svgHeightHalf, _v1, _v22, _v3, _svgNode, _pathCount = 0, _precision = null, _quality = 1, _currentPath, _currentStyle; const _this = this, _clipBox = new Box2(), _elemBox = new Box2(), _color2 = new Color$1(), _diffuseColor = new Color$1(), _ambientLight = new Color$1(), _directionalLights = new Color$1(), _pointLights = new Color$1(), _clearColor = new Color$1(), _vector3 = new Vector3(), _centroid = new Vector3(), _normal2 = new Vector3(), _normalViewMatrix = new Matrix3(), _viewMatrix = new Matrix4(), _viewProjectionMatrix = new Matrix4(), _svgPathPool = [], _projector = new Projector(), _svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); this.domElement = _svg; this.autoClear = true; this.sortObjects = true; this.sortElements = true; this.overdraw = 0.5; this.outputColorSpace = SRGBColorSpace; this.info = { render: { vertices: 0, faces: 0 } }; this.setQuality = function(quality) { switch (quality) { case "high": _quality = 1; break; case "low": _quality = 0; break; } }; this.setClearColor = function(color) { _clearColor.set(color); }; this.setPixelRatio = function() { }; this.setSize = function(width2, height2) { _svgWidth = width2; _svgHeight = height2; _svgWidthHalf = _svgWidth / 2; _svgHeightHalf = _svgHeight / 2; _svg.setAttribute("viewBox", -_svgWidthHalf + " " + -_svgHeightHalf + " " + _svgWidth + " " + _svgHeight); _svg.setAttribute("width", _svgWidth); _svg.setAttribute("height", _svgHeight); _clipBox.min.set(-_svgWidthHalf, -_svgHeightHalf); _clipBox.max.set(_svgWidthHalf, _svgHeightHalf); }; this.getSize = function() { return { width: _svgWidth, height: _svgHeight }; }; this.setPrecision = function(precision) { _precision = precision; }; function removeChildNodes() { _pathCount = 0; while (_svg.childNodes.length > 0) { _svg.removeChild(_svg.childNodes[0]); } } function convert(c2) { return _precision !== null ? c2.toFixed(_precision) : c2; } this.clear = function() { removeChildNodes(); _svg.style.backgroundColor = _clearColor.getStyle(_this.outputColorSpace); }; this.render = function(scene, camera) { if (camera instanceof Camera === false) { console.error("THREE.SVGRenderer.render: camera is not an instance of Camera."); return; } const background = scene.background; if (background && background.isColor) { removeChildNodes(); _svg.style.backgroundColor = background.getStyle(_this.outputColorSpace); } else if (this.autoClear === true) { this.clear(); } _this.info.render.vertices = 0; _this.info.render.faces = 0; _viewMatrix.copy(camera.matrixWorldInverse); _viewProjectionMatrix.multiplyMatrices(camera.projectionMatrix, _viewMatrix); _renderData = _projector.projectScene(scene, camera, this.sortObjects, this.sortElements); _elements = _renderData.elements; _lights = _renderData.lights; _normalViewMatrix.getNormalMatrix(camera.matrixWorldInverse); calculateLights(_lights); _currentPath = ""; _currentStyle = ""; for (let e = 0, el = _elements.length; e < el; e++) { const element = _elements[e]; const material = element.material; if (material === void 0 || material.opacity === 0) continue; _elemBox.makeEmpty(); if (element instanceof RenderableSprite) { _v1 = element; _v1.x *= _svgWidthHalf; _v1.y *= -_svgHeightHalf; renderSprite(_v1, element, material); } else if (element instanceof RenderableLine) { _v1 = element.v1; _v22 = element.v2; _v1.positionScreen.x *= _svgWidthHalf; _v1.positionScreen.y *= -_svgHeightHalf; _v22.positionScreen.x *= _svgWidthHalf; _v22.positionScreen.y *= -_svgHeightHalf; _elemBox.setFromPoints([_v1.positionScreen, _v22.positionScreen]); if (_clipBox.intersectsBox(_elemBox) === true) { renderLine(_v1, _v22, material); } } else if (element instanceof RenderableFace) { _v1 = element.v1; _v22 = element.v2; _v3 = element.v3; if (_v1.positionScreen.z < -1 || _v1.positionScreen.z > 1) continue; if (_v22.positionScreen.z < -1 || _v22.positionScreen.z > 1) continue; if (_v3.positionScreen.z < -1 || _v3.positionScreen.z > 1) continue; _v1.positionScreen.x *= _svgWidthHalf; _v1.positionScreen.y *= -_svgHeightHalf; _v22.positionScreen.x *= _svgWidthHalf; _v22.positionScreen.y *= -_svgHeightHalf; _v3.positionScreen.x *= _svgWidthHalf; _v3.positionScreen.y *= -_svgHeightHalf; if (this.overdraw > 0) { expand(_v1.positionScreen, _v22.positionScreen, this.overdraw); expand(_v22.positionScreen, _v3.positionScreen, this.overdraw); expand(_v3.positionScreen, _v1.positionScreen, this.overdraw); } _elemBox.setFromPoints([ _v1.positionScreen, _v22.positionScreen, _v3.positionScreen ]); if (_clipBox.intersectsBox(_elemBox) === true) { renderFace3(_v1, _v22, _v3, element, material); } } } flushPath(); scene.traverseVisible(function(object) { if (object.isSVGObject) { _vector3.setFromMatrixPosition(object.matrixWorld); _vector3.applyMatrix4(_viewProjectionMatrix); if (_vector3.z < -1 || _vector3.z > 1) return; const x2 = _vector3.x * _svgWidthHalf; const y2 = -_vector3.y * _svgHeightHalf; const node = object.node; node.setAttribute("transform", "translate(" + x2 + "," + y2 + ")"); _svg.appendChild(node); } }); }; function calculateLights(lights) { _ambientLight.setRGB(0, 0, 0); _directionalLights.setRGB(0, 0, 0); _pointLights.setRGB(0, 0, 0); for (let l = 0, ll = lights.length; l < ll; l++) { const light = lights[l]; const lightColor = light.color; if (light.isAmbientLight) { _ambientLight.r += lightColor.r; _ambientLight.g += lightColor.g; _ambientLight.b += lightColor.b; } else if (light.isDirectionalLight) { _directionalLights.r += lightColor.r; _directionalLights.g += lightColor.g; _directionalLights.b += lightColor.b; } else if (light.isPointLight) { _pointLights.r += lightColor.r; _pointLights.g += lightColor.g; _pointLights.b += lightColor.b; } } } function calculateLight(lights, position2, normal, color) { for (let l = 0, ll = lights.length; l < ll; l++) { const light = lights[l]; const lightColor = light.color; if (light.isDirectionalLight) { const lightPosition = _vector3.setFromMatrixPosition(light.matrixWorld).sub(_normal2.setFromMatrixPosition(light.target.matrixWorld)).normalize(); let amount = normal.dot(lightPosition); if (amount <= 0) continue; amount *= light.intensity; color.r += lightColor.r * amount; color.g += lightColor.g * amount; color.b += lightColor.b * amount; } else if (light.isPointLight) { const lightPosition = _vector3.setFromMatrixPosition(light.matrixWorld); let amount = normal.dot(_vector3.subVectors(lightPosition, position2).normalize()); if (amount <= 0) continue; amount *= light.distance == 0 ? 1 : 1 - Math.min(position2.distanceTo(lightPosition) / light.distance, 1); if (amount == 0) continue; amount *= light.intensity; color.r += lightColor.r * amount; color.g += lightColor.g * amount; color.b += lightColor.b * amount; } } } function renderSprite(v1, element, material) { let scaleX = element.scale.x * _svgWidthHalf; let scaleY = element.scale.y * _svgHeightHalf; if (material.isPointsMaterial) { scaleX *= material.size; scaleY *= material.size; } const path = "M" + convert(v1.x - scaleX * 0.5) + "," + convert(v1.y - scaleY * 0.5) + "h" + convert(scaleX) + "v" + convert(scaleY) + "h" + convert(-scaleX) + "z"; let style = ""; if (material.isSpriteMaterial || material.isPointsMaterial) { style = "fill:" + material.color.getStyle(_this.outputColorSpace) + ";fill-opacity:" + material.opacity; } addPath(style, path); } function renderLine(v1, v2, material) { const path = "M" + convert(v1.positionScreen.x) + "," + convert(v1.positionScreen.y) + "L" + convert(v2.positionScreen.x) + "," + convert(v2.positionScreen.y); if (material.isLineBasicMaterial) { let style = "fill:none;stroke:" + material.color.getStyle(_this.outputColorSpace) + ";stroke-opacity:" + material.opacity + ";stroke-width:" + material.linewidth + ";stroke-linecap:" + material.linecap; if (material.isLineDashedMaterial) { style = style + ";stroke-dasharray:" + material.dashSize + "," + material.gapSize; } addPath(style, path); } } function renderFace3(v1, v2, v3, element, material) { _this.info.render.vertices += 3; _this.info.render.faces++; const path = "M" + convert(v1.positionScreen.x) + "," + convert(v1.positionScreen.y) + "L" + convert(v2.positionScreen.x) + "," + convert(v2.positionScreen.y) + "L" + convert(v3.positionScreen.x) + "," + convert(v3.positionScreen.y) + "z"; let style = ""; if (material.isMeshBasicMaterial) { _color2.copy(material.color); if (material.vertexColors) { _color2.multiply(element.color); } } else if (material.isMeshLambertMaterial || material.isMeshPhongMaterial || material.isMeshStandardMaterial) { _diffuseColor.copy(material.color); if (material.vertexColors) { _diffuseColor.multiply(element.color); } _color2.copy(_ambientLight); _centroid.copy(v1.positionWorld).add(v2.positionWorld).add(v3.positionWorld).divideScalar(3); calculateLight(_lights, _centroid, element.normalModel, _color2); _color2.multiply(_diffuseColor).add(material.emissive); } else if (material.isMeshNormalMaterial) { _normal2.copy(element.normalModel).applyMatrix3(_normalViewMatrix).normalize(); _color2.setRGB(_normal2.x, _normal2.y, _normal2.z).multiplyScalar(0.5).addScalar(0.5); } if (material.wireframe) { style = "fill:none;stroke:" + _color2.getStyle(_this.outputColorSpace) + ";stroke-opacity:" + material.opacity + ";stroke-width:" + material.wireframeLinewidth + ";stroke-linecap:" + material.wireframeLinecap + ";stroke-linejoin:" + material.wireframeLinejoin; } else { style = "fill:" + _color2.getStyle(_this.outputColorSpace) + ";fill-opacity:" + material.opacity; } addPath(style, path); } function expand(v1, v2, pixels) { let x2 = v2.x - v1.x, y2 = v2.y - v1.y; const det = x2 * x2 + y2 * y2; if (det === 0) return; const idet = pixels / Math.sqrt(det); x2 *= idet; y2 *= idet; v2.x += x2; v2.y += y2; v1.x -= x2; v1.y -= y2; } function addPath(style, path) { if (_currentStyle === style) { _currentPath += path; } else { flushPath(); _currentStyle = style; _currentPath = path; } } function flushPath() { if (_currentPath) { _svgNode = getPathNode(_pathCount++); _svgNode.setAttribute("d", _currentPath); _svgNode.setAttribute("style", _currentStyle); _svg.appendChild(_svgNode); } _currentPath = ""; _currentStyle = ""; } function getPathNode(id) { if (_svgPathPool[id] == null) { _svgPathPool[id] = document.createElementNS("http://www.w3.org/2000/svg", "path"); if (_quality == 0) { _svgPathPool[id].setAttribute("shape-rendering", "crispEdges"); } return _svgPathPool[id]; } return _svgPathPool[id]; } } }; /** * @license * ts-browser-helpers v0.19.4 * Copyright 2022-2025 repalash <palash@shaders.app> * MIT License * See ./dependencies.txt for bundled third-party dependencies and licenses. */ function Fe(t, ...e) { return t; } function L(t, e) { return Object.hasOwn ? Object.hasOwn(t, e) : t.hasOwnProperty(e); } const k = class k2 { static callFunction(e, i, n = []) { if (!i) return e(...n); if (e.name && i[e.name] === e) return e.call(i, ...n); k2.methodMap.has(i) || k2.methodMap.set(i, /* @__PURE__ */ new WeakMap()); const r = k2.methodMap.get(i); if (!r.has(e)) { let o = i; for (; o; ) { const s = Object.values(Object.getOwnPropertyDescriptors(o)); for (let a of s) if (a.value === e) return r.set(e, true), e.call(i, ...n); o = Object.getPrototypeOf(o); } r.set(e, false); } return r.get(e) ? e.call(i, ...n) : e(...n); } }; k.methodMap = /* @__PURE__ */ new WeakMap(); let U$1 = k; function I(t, e = "param") { if (!t) throw new Error("onChange: fnKey is undefined, make sure the function exists or provide a string"); return (i, n, r) => { const o = { get() { return this[`_oc_${n}`]; }, set(s) { var u; const a = this[`_oc_${n}`]; if (a === s) return; this[`_oc_${n}`] = s; const c2 = e === "param" ? [n, s, a, this] : e === "object" ? [{ key: n, value: s, oldValue: a, target: this }] : []; typeof t == "string" ? (u = this[t]) == null || u.call(this, ...c2) : typeof t == "function" && U$1.callFunction(t, this, c2); } }; if (r) return L(r, "value") && delete r.value, L(r, "writable") && delete r.writable, L(r, "initializer") && delete r.initializer, Object.assign(r, o); Object.defineProperty(i, n, o); }; } function Ke(t, e = "void") { if (!t) throw new Error("onChange: fnKey is undefined, make sure the function exists or provide a string"); return I(t, e); } function xt() { const t = (n, r) => n.toString(16).padStart(r, "0"), e = crypto.getRandomValues(new Uint8Array(16)); e[6] = e[6] & 15 | 64, e[8] = e[8] & 63 | 128; const i = new DataView(e.buffer); return `${t(i.getUint32(0), 8)}-${t(i.getUint16(4), 4)}-${t(i.getUint16(6), 4)}-${t(i.getUint16(8), 4)}-${t(i.getUint32(10), 8)}${t(i.getUint16(14), 4)}`; } /** * @license * uiconfig.js v0.3.1 * Copyright 2022-2025 repalash <palash@shaders.app> * MIT License * See ./dependencies.txt for bundled third-party dependencies and licenses. */ const C = class C2 { }; C.Map = /* @__PURE__ */ new Map(); let c = C; function v(n, e, t, r, i = true) { r = r ?? n[e], t = t ?? e + ""; const o = r == null ? void 0 : r.uiConfig; let s; if (o) s = o; else { const a = T(r); a === "folder" ? s = U(t, r, {}, "folder", i) : a && (s = { type: a, label: t, property: [n, e] }); } return s && !s.label && (s.label = t), s; } function _(n) { if (!n) return []; let e = n.constructor || Object; e === Array && (e = Object); const t = [], r = []; for (; e && e !== Object; ) r.push(e), e = Object.getPrototypeOf(e); if (!r.length || Array.isArray(n)) { const i = typeof n == "object" ? Object.keys(n) : Array.isArray(n) ? n.map((o, s) => s) : []; for (const o of i) { const s = n[o]; if (s == null) continue; const a = v(n, o, o + "", s, true); a && t.push(a); } } return r.reverse().forEach((i) => { var o; (o = c.Map.get(i)) == null || o.forEach(({ params: s, propKey: a, uiType: u }) => { let d; if (u || (d = () => v(n, a)), d || (d = { property: [n, a], type: u || "input" // ...params, // ...extraParams, }), s) { const y2 = typeof s.params == "function" ? s.params(n) : s.params || {}, p = { ...s }; if (delete p.params, typeof d == "function") { const m = d; d = () => { const l = m(); return l && Object.assign(l, { ...p, ...y2 }); }; } else Object.assign(d, { ...p, ...y2 }); } t.push(d); }); }), t; } function U(n, e, t = {}, r = "folder", i = false) { return { type: r, label: n, children: i ? [() => _(e)] : _(e), uuid: xt(), ...t }; } function T(n) { return n == null ? null : Array.isArray(n) ? "folder" : typeof n == "boolean" ? "checkbox" : typeof n == "number" ? "number" : typeof n == "string" ? "input" : typeof n == "function" ? "button" : typeof n.x == "number" ? "vec" : typeof n.r == "number" ? "color" : n.isTexture ? "image" : typeof n == "object" ? "folder" : null; } function B(n) { return (e, t) => { const r = e.constructor; if (r === Object) throw new Error("Not possible to use uiConfig decorator on an object, use class instead"); c.Map.has(r) || c.Map.set(r, []), n(e, t, c.Map.get(r)); }; } function h(n, e) { return B((t, r, i) => { const o = i.findIndex((s) => s.propKey === r); if (o && o < 0) i.push({ params: e || {}, propKey: r, uiType: n }); else throw new Error(`Property ${r} already has a uiConfig decorator`); }); } function b(n, e, t = "panel") { return (r) => class extends r { constructor() { super(...arguments), this.uiConfig = U(Fe(n, this) || "", this, {}, t); } }; } function N(n, e, t) { return Array.isArray(e) && typeof e[0] == "string" && (e = e.map((r) => ({ label: r, value: r }))), h("dropdown", { label: n, children: e, params: t }); } function H(n, e) { return h("number", { label: n, params: e }); } function G$1(n, e) { return h("checkbox", { label: n, params: e }); } function X(n, e) { return b(n, e, "folder"); } var __defProp$2 = Object.defineProperty; var __getOwnPropDesc$2 = Object.getOwnPropertyDescriptor; var __decorateClass$2 = (decorators, target, key, kind) => { var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$2(target, key) : target; for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result; if (kind && result) __defProp$2(target, key, result); return result; }; let BasicSVGRenderer = class extends SVGRenderer$1 { constructor() { super(...arguments); this.quality = "high"; } _refresh() { this.setQuality(this.quality); } }; __decorateClass$2([ G$1() ], BasicSVGRenderer.prototype, "autoClear", 2); __decorateClass$2([ G$1() ], BasicSVGRenderer.prototype, "sortObjects", 2); __decorateClass$2([ G$1() ], BasicSVGRenderer.prototype, "sortElements", 2); __decorateClass$2([ H() ], BasicSVGRenderer.prototype, "overdraw", 2); __decorateClass$2([ N(void 0, ["low", "high"]), Ke(BasicSVGRenderer.prototype._refresh) ], BasicSVGRenderer.prototype, "quality", 2); BasicSVGRenderer = __decorateClass$2([ X("Basic SVG Renderer") ], BasicSVGRenderer); var __defProp$1 = Object.defineProperty; var __getOwnPropDesc$1 = Object.getOwnPropertyDescriptor; var __decorateClass$1 = (decorators, target, key, kind) => { var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$1(target, key) : target; for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result; if (kind && result) __defProp$1(target, key, result); return result; }; let BasicSVGRendererPlugin = class extends AViewerPluginSync { /** * @param enabled * @param autoAddToContainer - automatically add the svg to the viewer container and style it same as the viewer is position is absolute */ constructor(enabled = true, autoAddToContainer = true) { super(); this.autoAddToContainer = autoAddToContainer; this.enabled = true; this.renderer = new BasicSVGRenderer(); this._lastStyles = void 0; this.autoRender = true; this._viewerListeners = { postRender: (_2) => { if (this.autoRender) this.render(); } }; this._onResize = this._onResize.bind(this); this.enabled = enabled; this.renderer.domElement.style.position = "absolute"; this.renderer.domElement.style.display = "none"; } onAdded(viewer) { var _a; super.onAdded(viewer); this.renderer.setSize(viewer.canvas.clientWidth, viewer.canvas.clientHeight); this._refreshParams(); if (this.autoAddToContainer) { viewer.container.prepend(this.renderer.domElement); const element = this.renderer.domElement; element.style.pointerEvents = "none"; const canvasStyles = getComputedStyle(viewer.canvas); if (canvasStyles.position === "absolute") { this._lastStyles = element.style.cssText; element.style.top = canvasStyles.top; element.style.left = canvasStyles.left; element.style.width = canvasStyles.width; element.style.height = canvasStyles.height; } else { (_a = this._viewer) == null ? void 0 : _a.console.warn("BasicSVGRendererPlugin: canvas position should be absolute for proper rendering"); } viewer.renderManager.addEventListener("resize", this._onResize); } this.renderer.domElement.style.display = this.enabled ? "" : "none"; } onRemove(viewer) { super.onRemove(viewer); if (this.autoAddToContainer) { viewer.container.removeChild(this.renderer.domElement); } if (this._lastStyles !== void 0) { this.renderer.domElement.style.cssText = this._lastStyles; this._lastStyles = void 0; } viewer.renderManager.removeEventListener("resize", this._onResize); this.renderer.domElement.style.display = "none"; } render() { if (!this._viewer) return; if (this.isDisabled()) return; this.renderer.render(this._viewer.scene, this._viewer.scene.mainCamera); } download() { var _a; const svg2 = this.renderer.domElement.outerHTML; const blob = new Blob([svg2], { type: "image/svg+xml" }); (_a = this._viewer) == null ? void 0 : _a.exportBlob(blob, "scene.svg"); } get svgNode() { return this.renderer.domElement; } _refreshParams() { if (this.isDisabled()) return; this.renderer.setQuality("medium"); } _onResize() { if (!this._viewer) return; this.renderer.setSize(this._viewer.canvas.clientWidth, this._viewer.canvas.clientHeight); } }; BasicSVGRendererPlugin.PluginType = "BasicSVGRendererPlugin"; __decorateClass$1([ uiToggle() ], BasicSVGRendererPlugin.prototype, "enabled", 2); __decorateClass$1([ uiConfig() ], BasicSVGRendererPlugin.prototype, "renderer", 2); __decorateClass$1([ uiToggle() ], BasicSVGRendererPlugin.prototype, "autoRender", 2); __decorateClass$1([ uiButton() ], BasicSVGRendererPlugin.prototype, "render", 1); __decorateClass$1([ uiButton() ], BasicSVGRendererPlugin.prototype, "download", 1); BasicSVGRendererPlugin = __decorateClass$1([ uiFolderContainer("SVG Renderer") ], BasicSVGRendererPlugin); var ViewEdgeNature = /* @__PURE__ */ ((ViewEdgeNature2) => { ViewEdgeNature2["Silhouette"] = "Silhouette"; ViewEdgeNature2["Boundary"] = "Boundary"; ViewEdgeNature2["MeshIntersection"] = "MeshIntersection"; ViewEdgeNature2["Crease"] = "Crease"; ViewEdgeNature2["Material"] = "Material"; return ViewEdgeNature2; })(ViewEdgeNature || {}); const VisibilityIndicatingNatures = /* @__PURE__ */ new Set([ "Silhouette", "Boundary", "MeshIntersection" /* MeshIntersection */ ]); class ViewEdge { constructor(a, b2, nature, halfedge) { this.meshes = new Array(); this.faceAngle = Infinity; this.isBack = false; this.isConcave = false; this.faces = new Array(); this.a = a; this.b = b2; this.nature = nature; this.halfedge = halfedge; } get vertices() { return [this.a, this.b]; } get from() { return this.a.pos2d; } get to() { return this.b.pos2d; } toJSON() { return { id: [...this.a.vertices].map((v2) => v2.id).join(",") + "-" + [...this.b.vertices].map((v2) => v2.id).join(",") }; } clone() { const edge = new ViewEdge(this.a, this.b, this.nature, this.halfedge); edge.faceAngle = this.faceAngle; edge.isBack = this.isBack; edge.isConcave = this.isConcave; edge.meshes.push(...this.meshes); edge.faces.push(...this.faces); return edge; } otherVertex(vertex) { if (vertex === this.a) { return this.b; } else { return this.a; } } hasVertex(vertex) { return this.a === vertex || this.b === vertex; } isConnectedTo(edge) { return this.hasVertex(edge.a) || this.hasVertex(edge.b); } } function disposeMesh(mesh) { mesh.geometry.dispose(); if (mesh.material instanceof Array) { const materials = mesh.material; for (const material of materials) { material.dispose(); } } else { mesh.material.dispose(); } } function computeMorphedGeometry(source, target) { if (!source.geometry.hasAttribute("normal")) { source.geometry.computeVertexNormals(); } const { morphedPositionAttribute, morphedNormalAttribute } = computeMorphedAttributes(source); target.groups = [...source.geometry.groups]; if (source.geometry.index) { target.index = source.geometry.index.clone(); } target.deleteAttribute("position"); target.deleteAttribute("normal"); target.setAttribute("position", morphedPositionAttribute); target.setAttribute("normal", morphedNormalAttribute); } const _u$5 = new Vector3$1(); function projectPointNDC(point2, target, camera) { _u$5.copy(point2).project(camera); return target.set(_u$5.x, _u$5.y); } function projectPoint(point2, target, camera, renderSize) { projectPointNDC(point2, target, camera); NDCPointToImage(target, target, renderSize); return target; } function NDCPointToImage(point2, target, size2) { return target.set( (point2.x + 1) / 2 * size2.w, (1 - point2.y) / 2 * size2.h ); } function imagePointToNDC(point2, target, size2) { return target.set( 2 / size2.w * point2.x - 1, 1 - 2 / size2.h * point2.y ); } function hashVector3(vec, multiplier = 1e10) { const gap = 1e-3 / multiplier; return `${hashNumber(vec.x + gap, multiplier)},${hashNumber(vec.y + gap, multiplier)},${hashNumber(vec.z + gap, multiplier)}`; } function hashVector2(vec, multiplier = 1e10) { const gap = 1e-3 / multiplier; return `${hashNumber(vec.x + gap, multiplier)},${hashNumber(vec.y + gap, multiplier)}`; } function hashNumber(value, multiplier = 1e10) { return Math.trunc(value * multiplier); } function intersectLines(line1, line2, target, infiniteLine = false, tolerance = 1e-10) { const { x: x1, y: y1, z: z1 } = line1.start; const { x: x2, y: y2, z: z2 } = line1.end; const { x: x3, y: y3, z: z3 } = line2.start; const { x: x4, y: y4, z: z4 } = line2.end; const d1343 = (x1 - x3) * (x4 - x3) + (y1 - y3) * (y4 - y3) + (z1 - z3) * (z4 - z3); const d4321 = (x4 - x3) * (x2 - x1) + (y4 - y3) * (y2 - y1) + (z4 - z3) * (z2 - z1); const d1321 = (x1 - x3) * (x2 - x1) + (y1 - y3) * (y2 - y1) + (z1 - z3) * (z2 - z1); const d4343 = (x4 - x3) * (x4 - x3) + (y4 - y3) * (y4 - y3) + (z4 - z3) * (z4 - z3); const d2121 = (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1) + (z2 - z1) * (z2 - z1); const numerator = d1343 * d4321 - d1321 * d4343; const denominator = d2121 * d4343 - d4321 * d4321; if (denominator < tolerance) { return false; } const ta = numerator / denominator; const tb = (d1343 + ta * d4321) / d4343; if (!infiniteLine && (ta < 0 || ta > 1 || tb < 0 || tb > 1)) { return false; } const pax = x1 + ta * (x2 - x1); const pay = y1 + ta * (y2 - y1); const paz = z1 + ta * (z2 - z1); const pbx = x3 + tb * (x4 - x3); const pby = y3 + tb * (y4 - y3); const pbz = z3 + tb * (z4 - z3); if (Math.abs(pax - pbx) < tolerance && Math.abs(pay - pby) < tolerance && Math.abs(paz - pbz) < tolerance) { target.set(pax, pay, paz); return true; } return false; } function vectors3Equal(a, b2, tolerance = 1e-10) { return Math.abs(a.x - b2.x) < tolerance && Math.abs(a.y - b2.y) < tolerance && Math.abs(a.z - b2.z) < tolerance; } function vectors2Equal(a, b2, tolerance = 1e-10) { return Math.abs(a.x - b2.x) < tolerance && Math.abs(a.y - b2.y) < tolerance; } const _matrix$3 = new Matrix4$1(); function orient3D$2(a, b2, c2, d, epsilon = 1e-10) { _matrix$3.set( a.x, a.y, a.z, 1, b2.x, b2.y, b2.z, 1, c2.x, c2.y, c2.z, 1, d.x, d.y, d.z, 1 ); const det = _matrix$3.determinant(); if (det > epsilon) { return 1; } else if (det < -epsilon) { return -1; } return 0; } function frontSide$1(a, b2, c2, d, epsilon = 1e-10) { return orient3D$2(d, b2, c2, a, epsilon) > 0; } function sameSide(a, b2, c2, d, e, epsilon = 1e-10) { return orient3D$2(a, b2, c2, d, epsilon) > 0 === orient3D$2(a, b2, c2, e, epsilon) > 0; } function round(num, divider = 100) { return Math.round(num * divider) / divider; } var ViewVertexSingularity = /* @__PURE__ */ ((ViewVertexSingularity2) => { ViewVertexSingularity2["None"] = "None"; ViewVertexSingularity2["ImageIntersection"] = "ImageIntersection"; ViewVertexSingularity2["MeshIntersection"] = "MeshIntersection"; ViewVertexSingularity2["CurtainFold"] = "CurtainFold"; ViewVertexSingularity2["Bifurcation"] = "Bifurcation"; return ViewVertexSingularity2; })(ViewVertexSingularity || {}); class ViewVertex { constructor() { this.hash3d = ""; this.hash2d = ""; this.singular