@threepipe/plugin-svg-renderer
Version:
Plugins for SVG Rendering of 3d objects for Threepipe
1,224 lines (1,223 loc) • 1.16 MB
JavaScript
(function(global, factory) {
typeof exports === "object" && typeof module !== "undefined" ? factory(exports, require("threepipe"), require("three")) : typeof define === "function" && define.amd ? define(["exports", "threepipe", "three"], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global["@threepipe/plugin-svg-renderer"] = {}, global.threepipe, global.threepipe));
})(this, function(exports2, threepipe, three) {
"use strict";/**
* @license
* @threepipe/plugin-svg-renderer v0.2.3
* Copyright 2022-2025 repalash <palash@shaders.app>
* GPLV3 License
* See ./dependencies.txt for any bundled third-party dependencies and licenses.
*/
var _documentCurrentScript = typeof document !== "undefined" ? document.currentScript : null;
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 threepipe.Vector3();
this.vertexNormalsModel = [new threepipe.Vector3(), new threepipe.Vector3(), new threepipe.Vector3()];
this.vertexNormalsLength = 0;
this.color = new threepipe.Color();
this.material = null;
this.uvs = [new threepipe.Vector2(), new threepipe.Vector2(), new threepipe.Vector2()];
this.z = 0;
this.renderOrder = 0;
}
}
class RenderableVertex {
constructor() {
this.position = new threepipe.Vector3();
this.positionWorld = new threepipe.Vector3();
this.positionScreen = new threepipe.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 threepipe.Color(), new threepipe.Color()];
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 threepipe.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 threepipe.Vector3(), _vector4 = new threepipe.Vector4(), _clipBox = new threepipe.Box3(new threepipe.Vector3(-1, -1, -1), new threepipe.Vector3(1, 1, 1)), _boundingBox = new threepipe.Box3(), _points3 = new Array(3), _viewMatrix = new threepipe.Matrix4(), _viewProjectionMatrix = new threepipe.Matrix4(), _modelViewProjectionMatrix = new threepipe.Matrix4(), _frustum = new threepipe.Frustum(), _objectPool = [], _vertexPool = [], _facePool = [], _linePool = [], _spritePool = [];
function RenderList() {
const normals = [];
const colors = [];
const uvs = [];
let object = null;
const normalMatrix = new threepipe.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, b) {
colors.push(r, g, b);
}
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, b) {
const v1 = _vertexPool[a];
const v2 = _vertexPool[b];
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, b * 3);
}
_renderData.elements.push(_line2);
}
}
function pushTriangle(a, b, c, material) {
const v1 = _vertexPool[a];
const v2 = _vertexPool[b];
const v3 = _vertexPool[c];
if (checkTriangleVisibility(v1, v2, v3) === false) return;
if (material.side === threepipe.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, b) {
if (a.renderOrder !== b.renderOrder) {
return a.renderOrder - b.renderOrder;
} else if (a.z !== b.z) {
return b.z - a.z;
} else if (a.id !== b.id) {
return a.id - b.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 threepipe.Box2(), _elemBox = new threepipe.Box2(), _color2 = new threepipe.Color(), _diffuseColor = new threepipe.Color(), _ambientLight = new threepipe.Color(), _directionalLights = new threepipe.Color(), _pointLights = new threepipe.Color(), _clearColor = new threepipe.Color(), _vector3 = new threepipe.Vector3(), _centroid = new threepipe.Vector3(), _normal2 = new threepipe.Vector3(), _normalViewMatrix = new threepipe.Matrix3(), _viewMatrix = new threepipe.Matrix4(), _viewProjectionMatrix = new threepipe.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.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(c) {
return _precision !== null ? c.toFixed(_precision) : c;
}
this.clear = function() {
removeChildNodes();
_svg.style.backgroundColor = _clearColor.getStyle();
};
this.render = function(scene, camera) {
if (camera instanceof threepipe.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();
} 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() + ";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() + ";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() + ";stroke-opacity:" + material.opacity + ";stroke-width:" + material.wireframeLinewidth + ";stroke-linecap:" + material.wireframeLinecap + ";stroke-linejoin:" + material.wireframeLinejoin;
} else {
style = "fill:" + _color2.getStyle() + ";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
* uiconfig.js v0.1.3
* Copyright 2022-2025 repalash <palash@shaders.app>
* MIT License
*/
function Ee(n, ...e) {
return typeof n == "function" && (n = n(...e)), n;
}
function it() {
const n = (i, r) => i.toString(16).padStart(r, "0"), e = crypto.getRandomValues(new Uint8Array(16));
e[6] = e[6] & 15 | 64, e[8] = e[8] & 63 | 128;
const t = new DataView(e.buffer);
return `${n(t.getUint32(0), 8)}-${n(t.getUint16(4), 4)}-${n(t.getUint16(6), 4)}-${n(t.getUint16(8), 4)}-${n(t.getUint32(10), 8)}${n(t.getUint16(14), 4)}`;
}
class UiConfigTypeMap {
}
UiConfigTypeMap.Map = /* @__PURE__ */ new Map();
function generateValueConfig(obj, key, label, val) {
val = val !== null && val !== void 0 ? val : obj[key];
const config = val === null || val === void 0 ? void 0 : val.uiConfig;
let result = void 0;
if (config) {
result = config;
} else {
const uiType = valueToUiType(val);
if (uiType === "folder") {
result = generateUiFolder(key + "", val, {}, "folder", true);
} else if (uiType)
result = {
type: uiType,
label: key + "",
property: [obj, key]
};
}
label = label !== null && label !== void 0 ? label : key + "";
if (result && !result.label)
result.label = label;
return result;
}
function generateUiConfig(obj) {
if (!obj)
return [];
let type = obj.constructor || Object;
if (type === Array)
type = Object;
const result = [];
const types = [];
while (type && type !== Object) {
types.push(type);
type = Object.getPrototypeOf(type);
}
if (!types.length || Array.isArray(obj)) {
const keys = typeof obj === "object" ? Object.keys(obj) : Array.isArray(obj) ? obj.map((_, i) => i) : [];
for (const key of keys) {
const val = obj[key];
if (val === void 0 || val === null)
continue;
const c = () => generateValueConfig(obj, key, key + "", val);
result.push(c);
}
}
types.reverse().forEach((t) => {
var _a;
(_a = UiConfigTypeMap.Map.get(t)) === null || _a === void 0 ? void 0 : _a.forEach(({ params, propKey, uiType }) => {
let config;
if (!uiType) {
config = () => generateValueConfig(obj, propKey);
}
if (!config) {
config = {
property: [obj, propKey],
type: uiType || "input"
// ...params,
// ...extraParams,
};
}
if (params) {
const extraParams = typeof params.params === "function" ? params.params(obj) : params.params || {};
const params1 = Object.assign({}, params);
delete params1.params;
if (typeof config === "function") {
const c1 = config;
config = () => Object.assign(c1(), Object.assign(Object.assign({}, params1), extraParams));
}
Object.assign(config, Object.assign(Object.assign({}, params1), extraParams));
}
result.push(config);
});
});
return result;
}
function generateUiFolder(label, obj, params = {}, type = "folder", dynamic = false) {
return Object.assign({ type, label, children: !dynamic ? generateUiConfig(obj) : () => generateUiConfig(obj), uuid: it() }, params);
}
function valueToUiType(val) {
if (val === null || val === void 0)
return null;
if (Array.isArray(val))
return "folder";
if (typeof val === "boolean")
return "checkbox";
if (typeof val === "number")
return "number";
if (typeof val === "string")
return "input";
if (typeof val === "function")
return "button";
if (typeof val.x === "number")
return "vec";
if (typeof val.r === "number")
return "color";
if (val.isTexture)
return "image";
if (typeof val === "object")
return "folder";
return null;
}
function uiConfigDecorator(action) {
return (targetPrototype, propertyKey) => {
const type = targetPrototype.constructor;
if (type === Object)
throw new Error("Not possible to use uiConfig decorator on an object, use class instead");
if (!UiConfigTypeMap.Map.has(type))
UiConfigTypeMap.Map.set(type, []);
action(targetPrototype, propertyKey, UiConfigTypeMap.Map.get(type));
};
}
function uiConfig(uiType, params) {
return uiConfigDecorator((_, propertyKey, uiConfigs) => {
const index = uiConfigs.findIndex((item) => item.propKey === propertyKey);
if (index && index < 0)
uiConfigs.push({
params: params || {},
propKey: propertyKey,
uiType
});
else {
throw new Error(`Property ${propertyKey} already has a uiConfig decorator`);
}
});
}
function uiContainer(label, params, type = "panel") {
return (constructor) => {
return class extends constructor {
constructor() {
super(...arguments);
this.uiConfig = generateUiFolder(Ee(label, this) || "", this, {}, type);
}
};
};
}
function uiDropdown(label, children, params) {
if (Array.isArray(children) && typeof children[0] === "string")
children = children.map((c) => ({ label: c, value: c }));
return uiConfig("dropdown", { label, children, params });
}
function uiNumber(label, params) {
return uiConfig("number", { label, params });
}
function uiToggle(label, params) {
return uiConfig("checkbox", { label, params });
}
function uiFolderContainer(label, params) {
return uiContainer(label, params, "folder");
}
typeof SuppressedError === "function" ? SuppressedError : function(error, suppressed, message) {
var e = new Error(message);
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
};
/**
* @license
* ts-browser-helpers v0.16.2
* Copyright 2022-2025 repalash <palash@shaders.app>
* MIT License
* See ./dependencies.txt for bundled third-party dependencies and licenses.
*/
function v(n, e) {
return Object.hasOwn ? Object.hasOwn(n, e) : n.hasOwnProperty(e);
}
const m = class m2 {
static callFunction(e, t, i = []) {
if (!t) return e(...i);
if (e.name && t[e.name] === e) return e.call(t, ...i);
m2.methodMap.has(t) || m2.methodMap.set(t, /* @__PURE__ */ new WeakMap());
const r = m2.methodMap.get(t);
if (!r.has(e)) {
let s = t;
for (; s; ) {
const o = Object.values(Object.getOwnPropertyDescriptors(s));
for (let c of o)
if (c.value === e)
return r.set(e, true), e.call(t, ...i);
s = Object.getPrototypeOf(s);
}
r.set(e, false);
}
return r.get(e) ? e.call(t, ...i) : e(...i);
}
};
m.methodMap = /* @__PURE__ */ new WeakMap();
let E = m;
function x$4(n, e = "param") {
if (!n) throw new Error("onChange: fnKey is undefined, make sure the function exists or provide a string");
return (t, i, r) => {
const s = {
get() {
return this[`_oc_${i}`];
},
set(o) {
var u;
const c = this[`_oc_${i}`];
if (c === o) return;
this[`_oc_${i}`] = o;
const a = e === "param" ? [i, o, c, this] : e === "object" ? [{ key: i, value: o, oldValue: c, target: this }] : [];
typeof n == "string" ? (u = this[n]) == null || u.call(this, ...a) : typeof n == "function" && E.callFunction(n, this, a);
}
};
if (r)
return v(r, "value") && delete r.value, v(r, "writable") && delete r.writable, v(r, "initializer") && delete r.initializer, Object.assign(r, s);
Object.defineProperty(t, i, s);
};
}
function ze(n, e = "void") {
if (!n) throw new Error("onChange: fnKey is undefined, make sure the function exists or provide a string");
return x$4(n, e);
}
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;
};
exports2.BasicSVGRenderer = class BasicSVGRenderer extends SVGRenderer$1 {
constructor() {
super(...arguments);
this.quality = "high";
}
_refresh() {
this.setQuality(this.quality);
}
};
__decorateClass$2([
uiToggle()
], exports2.BasicSVGRenderer.prototype, "autoClear", 2);
__decorateClass$2([
uiToggle()
], exports2.BasicSVGRenderer.prototype, "sortObjects", 2);
__decorateClass$2([
uiToggle()
], exports2.BasicSVGRenderer.prototype, "sortElements", 2);
__decorateClass$2([
uiNumber()
], exports2.BasicSVGRenderer.prototype, "overdraw", 2);
__decorateClass$2([
uiDropdown(void 0, ["low", "high"]),
ze(exports2.BasicSVGRenderer.prototype._refresh)
], exports2.BasicSVGRenderer.prototype, "quality", 2);
exports2.BasicSVGRenderer = __decorateClass$2([
uiFolderContainer("Basic SVG Renderer")
], exports2.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;
};
exports2.BasicSVGRendererPlugin = class BasicSVGRendererPlugin extends threepipe.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 exports2.BasicSVGRenderer();
this._lastStyles = void 0;
this.autoRender = true;
this._viewerListeners = {
postRender: (_) => {
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);
}
};
exports2.BasicSVGRendererPlugin.PluginType = "BasicSVGRendererPlugin";
__decorateClass$1([
threepipe.uiToggle()
], exports2.BasicSVGRendererPlugin.prototype, "enabled", 2);
__decorateClass$1([
threepipe.uiConfig()
], exports2.BasicSVGRendererPlugin.prototype, "renderer", 2);
__decorateClass$1([
threepipe.uiToggle()
], exports2.BasicSVGRendererPlugin.prototype, "autoRender", 2);
__decorateClass$1([
threepipe.uiButton()
], exports2.BasicSVGRendererPlugin.prototype, "render", 1);
__decorateClass$1([
threepipe.uiButton()
], exports2.BasicSVGRendererPlugin.prototype, "download", 1);
exports2.BasicSVGRendererPlugin = __decorateClass$1([
threepipe.uiFolderContainer("SVG Renderer")
], exports2.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, b, nature, halfedge) {
this.meshes = new Array();
this.faceAngle = Infinity;
this.isBack = false;
this.isConcave = false;
this.faces = new Array();
this.a = a;
this.b = b;
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.