313 lines
13 KiB
JavaScript
313 lines
13 KiB
JavaScript
"use strict";
|
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
const THREE = require("three");
|
|
const Projector = require("./Projector.cjs");
|
|
class SVGObject extends THREE.Object3D {
|
|
constructor(node) {
|
|
super();
|
|
this.isSVGObject = true;
|
|
this.node = node;
|
|
}
|
|
}
|
|
class SVGRenderer {
|
|
constructor() {
|
|
let _renderData, _elements, _lights, _svgWidth, _svgHeight, _svgWidthHalf, _svgHeightHalf, _v1, _v2, _v3, _svgNode, _pathCount = 0, _precision = null, _quality = 1, _currentPath, _currentStyle;
|
|
const _this = this, _clipBox = new THREE.Box2(), _elemBox = new THREE.Box2(), _color = new THREE.Color(), _diffuseColor = new THREE.Color(), _ambientLight = new THREE.Color(), _directionalLights = new THREE.Color(), _pointLights = new THREE.Color(), _clearColor = new THREE.Color(), _vector3 = new THREE.Vector3(), _centroid = new THREE.Vector3(), _normal = new THREE.Vector3(), _normalViewMatrix = new THREE.Matrix3(), _viewMatrix = new THREE.Matrix4(), _viewProjectionMatrix = new THREE.Matrix4(), _svgPathPool = [], _projector = new Projector.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(width, height) {
|
|
_svgWidth = width;
|
|
_svgHeight = height;
|
|
_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 THREE.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 Projector.RenderableSprite) {
|
|
_v1 = element;
|
|
_v1.x *= _svgWidthHalf;
|
|
_v1.y *= -_svgHeightHalf;
|
|
renderSprite(_v1, element, material);
|
|
} else if (element instanceof Projector.RenderableLine) {
|
|
_v1 = element.v1;
|
|
_v2 = element.v2;
|
|
_v1.positionScreen.x *= _svgWidthHalf;
|
|
_v1.positionScreen.y *= -_svgHeightHalf;
|
|
_v2.positionScreen.x *= _svgWidthHalf;
|
|
_v2.positionScreen.y *= -_svgHeightHalf;
|
|
_elemBox.setFromPoints([_v1.positionScreen, _v2.positionScreen]);
|
|
if (_clipBox.intersectsBox(_elemBox) === true) {
|
|
renderLine(_v1, _v2, material);
|
|
}
|
|
} else if (element instanceof Projector.RenderableFace) {
|
|
_v1 = element.v1;
|
|
_v2 = element.v2;
|
|
_v3 = element.v3;
|
|
if (_v1.positionScreen.z < -1 || _v1.positionScreen.z > 1)
|
|
continue;
|
|
if (_v2.positionScreen.z < -1 || _v2.positionScreen.z > 1)
|
|
continue;
|
|
if (_v3.positionScreen.z < -1 || _v3.positionScreen.z > 1)
|
|
continue;
|
|
_v1.positionScreen.x *= _svgWidthHalf;
|
|
_v1.positionScreen.y *= -_svgHeightHalf;
|
|
_v2.positionScreen.x *= _svgWidthHalf;
|
|
_v2.positionScreen.y *= -_svgHeightHalf;
|
|
_v3.positionScreen.x *= _svgWidthHalf;
|
|
_v3.positionScreen.y *= -_svgHeightHalf;
|
|
if (this.overdraw > 0) {
|
|
expand(_v1.positionScreen, _v2.positionScreen, this.overdraw);
|
|
expand(_v2.positionScreen, _v3.positionScreen, this.overdraw);
|
|
expand(_v3.positionScreen, _v1.positionScreen, this.overdraw);
|
|
}
|
|
_elemBox.setFromPoints([_v1.positionScreen, _v2.positionScreen, _v3.positionScreen]);
|
|
if (_clipBox.intersectsBox(_elemBox) === true) {
|
|
renderFace3(_v1, _v2, _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 x = _vector3.x * _svgWidthHalf;
|
|
const y = -_vector3.y * _svgHeightHalf;
|
|
const node = object.node;
|
|
node.setAttribute("transform", "translate(" + x + "," + y + ")");
|
|
_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, position, 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).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, position).normalize());
|
|
if (amount <= 0)
|
|
continue;
|
|
amount *= light.distance == 0 ? 1 : 1 - Math.min(position.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) {
|
|
_color.copy(material.color);
|
|
if (material.vertexColors) {
|
|
_color.multiply(element.color);
|
|
}
|
|
} else if (material.isMeshLambertMaterial || material.isMeshPhongMaterial || material.isMeshStandardMaterial) {
|
|
_diffuseColor.copy(material.color);
|
|
if (material.vertexColors) {
|
|
_diffuseColor.multiply(element.color);
|
|
}
|
|
_color.copy(_ambientLight);
|
|
_centroid.copy(v1.positionWorld).add(v2.positionWorld).add(v3.positionWorld).divideScalar(3);
|
|
calculateLight(_lights, _centroid, element.normalModel, _color);
|
|
_color.multiply(_diffuseColor).add(material.emissive);
|
|
} else if (material.isMeshNormalMaterial) {
|
|
_normal.copy(element.normalModel).applyMatrix3(_normalViewMatrix).normalize();
|
|
_color.setRGB(_normal.x, _normal.y, _normal.z).multiplyScalar(0.5).addScalar(0.5);
|
|
}
|
|
if (material.wireframe) {
|
|
style = "fill:none;stroke:" + _color.getStyle() + ";stroke-opacity:" + material.opacity + ";stroke-width:" + material.wireframeLinewidth + ";stroke-linecap:" + material.wireframeLinecap + ";stroke-linejoin:" + material.wireframeLinejoin;
|
|
} else {
|
|
style = "fill:" + _color.getStyle() + ";fill-opacity:" + material.opacity;
|
|
}
|
|
addPath(style, path);
|
|
}
|
|
function expand(v1, v2, pixels) {
|
|
let x = v2.x - v1.x, y = v2.y - v1.y;
|
|
const det = x * x + y * y;
|
|
if (det === 0)
|
|
return;
|
|
const idet = pixels / Math.sqrt(det);
|
|
x *= idet;
|
|
y *= idet;
|
|
v2.x += x;
|
|
v2.y += y;
|
|
v1.x -= x;
|
|
v1.y -= y;
|
|
}
|
|
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];
|
|
}
|
|
}
|
|
}
|
|
exports.SVGObject = SVGObject;
|
|
exports.SVGRenderer = SVGRenderer;
|
|
//# sourceMappingURL=SVGRenderer.cjs.map
|