three-wideline
Version:
A three js line implementation
646 lines (629 loc) • 34.4 kB
JavaScript
import React from 'react';
import * as THREE from 'three';
import { FrontSide, DoubleSide, Vector3, Matrix4, Ray, Vector2, Color, Sphere, Box3 } from 'three';
/******************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
var __assign = function() {
__assign = Object.assign || function __assign(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var vertexSimple = "attribute vec3 pointA;\nattribute vec3 pointB;\n\nuniform float width;\nuniform float zlevel;\n\nvoid main() {\n vec2 pA = pointA.xy;\n vec2 pB = pointB.xy;\n\n vec2 xBasis = pB - pA;\n vec2 yBasis = normalize(vec2(-xBasis.y, xBasis.x));\n vec2 point = pA + xBasis * position.x + yBasis * width * position.y;\n\n vec3 transformed = vec3(point, zlevel);\n vec3 objectNormal = normalize(vec3(transformed.xy, 1));\n}\n";
var vertexStrip = "attribute vec3 pointA;\nattribute vec3 pointB;\nattribute vec3 pointC;\nattribute vec3 pointD;\n\nuniform float opacity;\nuniform float width;\nuniform float zlevel;\n\nvoid main() {\n vec2 pA = pointA.xy;\n vec2 pB = pointB.xy;\n vec2 pC = pointC.xy;\n vec2 pD = pointD.xy;\n\n // Select the three points we'll use and adjust the vertex according to \n // the side of the segment the vertex is on and the order of the points.\n vec2 p0 = pA;\n vec2 p1 = pB;\n vec2 p2 = pC;\n vec2 pos = position.xy;\n if(position.x == 1.0) {\n p0 = pD;\n p1 = pC;\n p2 = pB;\n pos = vec2(1.0 - position.x, -position.y);\n }\n\n // Find the normal vector.\n vec2 tangent = normalize(normalize(p2 - p1) + normalize(p1 - p0));\n vec2 normal = vec2(-tangent.y, tangent.x);\n\n // Find the perpendicular vectors.\n vec2 p01 = p1 - p0;\n vec2 p21 = p1 - p2;\n vec2 p01Norm = normalize(vec2(-p01.y, p01.x));\n\n // Determine the bend direction.\n float sigma = sign(dot(p01 + p21, normal));\n\n // If this is the intersecting vertex, \n vec3 transformed;\n\n if(sign(pos.y) == -sigma) {\n float mx = 0.5;\n float dt = max(mx, dot(normal, p01Norm));\n vec2 point = 0.5 * normal * -sigma * width / dt;\n transformed = vec3(p1 + point, zlevel);\n } else {\n vec2 xBasis = p2 - p1;\n vec2 yBasis = normalize(vec2(-xBasis.y, xBasis.x));\n vec2 point = p1 + xBasis * pos.x + yBasis * width * pos.y;\n transformed = vec3(point, zlevel);\n }\n vec3 objectNormal = normalize(vec3(transformed.xy, 1));\n}\n";
var vertexStrip1st = "attribute vec3 pointA;\nattribute vec3 pointB;\nattribute vec3 pointC;\n\nuniform float width;\nuniform float zlevel;\n\nvoid main() {\n vec2 pA = pointA.xy;\n vec2 pB = pointB.xy;\n vec2 pC = pointC.xy;\n\n vec3 transformed;\n\n if(position.x == 0.0) {\n vec2 xBasis = pB - pA;\n vec2 yBasis = normalize(vec2(-xBasis.y, xBasis.x));\n vec2 point = pA + xBasis * position.x + yBasis * width * position.y;\n transformed = vec3(point, zlevel);\n } else {\n\n // Find the normal vector.\n vec2 tangent = normalize(normalize(pC - pB) + normalize(pB - pA));\n vec2 normal = vec2(-tangent.y, tangent.x);\n\n // Find the perpendicular vectors.\n vec2 ab = pB - pA;\n vec2 cb = pB - pC;\n vec2 abNorm = normalize(vec2(-ab.y, ab.x));\n\n // Determine the bend direction.\n float sigma = sign(dot(ab + cb, normal));\n\n if(sign(position.y) == -sigma) {\n float mx = 0.5;\n float dt = max(mx, dot(normal, abNorm));\n vec2 position = 0.5 * normal * -sigma * width / dt;\n transformed = vec3(pB + position, zlevel);\n } else {\n vec2 xBasis = pB - pA;\n vec2 yBasis = normalize(vec2(-xBasis.y, xBasis.x));\n vec2 point = pA + xBasis * position.x + yBasis * width * position.y;\n transformed = vec3(point, zlevel);\n }\n }\n vec3 objectNormal = normalize(vec3(transformed.xy, 1));\n}\n";
var vertexCaps = "attribute vec3 pointA;\nattribute vec3 pointB;\n\nuniform float width;\nuniform float dir; // <=0 start cap, >0 end cap\nuniform float zlevel;\n\nvoid main() {\n vec2 pA = pointA.xy;\n vec2 pB = pointB.xy;\n\n vec2 xBasis = normalize(dir <= 0.0 ? (pA - pB) : (pB - pA));\n vec2 yBasis = vec2(-xBasis.y, xBasis.x);\n vec2 point = (dir <= 0.0 ? pA : pB) + xBasis * width * position.x + yBasis * width * position.y;\n\n vec3 transformed = vec3(point, zlevel);\n vec3 objectNormal = normalize(vec3(transformed.xy, 1));\n}\n";
var vertexRoundJoin = "attribute vec3 pointA;\nattribute vec3 pointB;\nattribute vec3 pointC;\n\nuniform float width;\nuniform float resolution;\nuniform float zlevel;\n\nvoid main() {\n vec2 pA = pointA.xy;\n vec2 pB = pointB.xy;\n vec2 pC = pointC.xy;\n\n // Calculate the x- and y- basis vectors.\n vec2 xBasis = normalize(normalize(pC - pB) + normalize(pB - pA));\n vec2 yBasis = vec2(-xBasis.y, xBasis.x);\n\n // Calculate the normal vectors for each neighboring segment.\n vec2 ab = pB - pA;\n vec2 cb = pB - pC;\n vec2 abn = normalize(vec2(-ab.y, ab.x));\n vec2 cbn = -normalize(vec2(-cb.y, cb.x));\n\n // Determine the direction of the bend.\n float sigma = sign(dot(ab + cb, yBasis));\n vec3 transformed;\n // If this is the zeroth id, it's the center of our circle. Stretch it to meet the segments' intersection.\n if(position.x == 0.0) {\n float mx = 0.5;\n transformed = vec3(pB + -0.5 * yBasis * sigma * width / max(mx, dot(yBasis, abn)), zlevel);\n } else {\n\n // Otherwise find the angle for this vertex.\n float theta = acos(dot(abn, cbn));\n theta = (sigma * 0.5 * 3.1515926) + -0.5 * theta + theta * (position.x - 1.0) / resolution;\n\n // Find the vertex position from the angle and multiply it by our basis vectors.\n vec2 pos = 0.5 * width * vec2(cos(theta), sin(theta));\n pos = pB + xBasis * pos.x + yBasis * pos.y;\n\n transformed = vec3(pos, zlevel);\n }\n vec3 objectNormal = normalize(vec3(transformed.xy, 1));\n}\n";
var vertexBevel = "attribute vec3 pointA;\nattribute vec3 pointB;\nattribute vec3 pointC;\n\nuniform float opacity;\nuniform float width;\nuniform float zlevel;\n\nvoid main() {\n vec2 pA = pointA.xy;\n vec2 pB = pointB.xy;\n vec2 pC = pointC.xy;\n vec2 tangent = normalize(normalize(pC - pB) + normalize(pB - pA));\n vec2 normal = vec2(-tangent.y, tangent.x);\n vec2 ab = pB - pA;\n vec2 cb = pB - pC;\n float sigma = sign(dot(ab + cb, normal));\n vec2 abn = normalize(vec2(-ab.y, ab.x));\n vec2 cbn = -normalize(vec2(-cb.y, cb.x));\n vec2 p0 = 0.5 * sigma * width * (sigma < 0.0 ? abn : cbn);\n vec2 p1 = 0.5 * sigma * width * (sigma < 0.0 ? cbn : abn);\n float mx = 0.5;\n float dt = max(mx, dot(normal, abn));\n vec2 p2 = -0.5 * normal * sigma * width / dt;\n vec2 point = pB + position.x * p0 + position.y * p1 + position.z * p2;\n\n vec3 transformed = vec3(point, zlevel);\n vec3 objectNormal = normalize(vec3(transformed.xy, 1));\n}\n";
var vertexMiter = "attribute vec3 pointA;\nattribute vec3 pointB;\nattribute vec3 pointC;\n\nuniform float opacity;\nuniform float width;\nuniform float zlevel;\n\nvoid main() {\n vec2 pA = pointA.xy;\n vec2 pB = pointB.xy;\n vec2 pC = pointC.xy;\n // Find the miter vector.\n vec2 tangent = normalize(normalize(pC - pB) + normalize(pB - pA));\n vec2 miter = vec2(-tangent.y, tangent.x);\n\n // Find the perpendicular vectors.\n vec2 ab = pB - pA;\n vec2 cb = pB - pC;\n vec2 abNorm = normalize(vec2(-ab.y, ab.x));\n vec2 cbNorm = -normalize(vec2(-cb.y, cb.x));\n\n // Determine the bend direction.\n float sigma = sign(dot(ab + cb, miter));\n\n // Calculate the basis vectors for the miter geometry.\n vec2 p0 = 0.5 * width * sigma * (sigma < 0.0 ? abNorm : cbNorm);\n vec2 p1 = 0.5 * miter * sigma * width / max(0.25, dot(miter, abNorm));\n vec2 p2 = 0.5 * width * sigma * (sigma < 0.0 ? cbNorm : abNorm);\n float mx = 0.5;\n vec2 p3 = -0.5 * miter * sigma * width / max(mx, dot(miter, abNorm));\n float w = (position == vec3(0, 0, 0)) ? 1.0 : 0.0; // to avoid position definition as vec4 type\n\n // Calculate the final point position.\n vec2 point = pB + position.x * p0 + position.y * p1 + position.z * p2 + w * p3;\n\n vec3 transformed = vec3(point, zlevel);\n vec3 objectNormal = normalize(vec3(transformed.xy, 1));\n}\n";
/**
* @internal
* Contains line scheme geometry creator.
* This is a toolset to create the needed meshs for a whole line representation.
*/
var Scheme = /** @class */ (function () {
function Scheme() {
var _this = this;
this.data = {
vertices: [],
shader: [],
};
this.transparency = false;
/**
* Create simple line segments.
* A list of meshes like rectangles.
* At the joins, the rectangles are overlapped.
*/
this.simple = function (props) {
var geometry = boxGeometry();
_this.addGeometry(geometry);
_this.addUniforms("simple", vertexSimple, props);
};
/**
* Create advanced line segments. Instead rectangles trapezoids created.
* At the joins, these meshes are not overlapped.
* Strips are used to draw transparent lines.
*/
this.strip = function (props) {
_this.stripMain(props);
_this.strip1st(props);
};
/** draw all strips except first element */
this.stripMain = function (props) {
var geometry = boxGeometry();
_this.addGeometry(geometry);
_this.addUniforms("strip", vertexStrip, props);
};
/** draw only the first strip element */
this.strip1st = function (props) {
var geometry = boxGeometry();
_this.addGeometry(geometry, "Start");
_this.addUniforms("strip1st", vertexStrip1st, props);
};
/** Add custom meshes */
this.custom = function (props, geometry) {
_this.addGeometry(geometry);
_this.addUniforms("simple", vertexSimple, [props]);
};
/** Add bevil joins. */
this.bevel = function (props) {
var geometry = {
positions: [
[1, 0, 0],
[0, 1, 0],
[0, 0, 1],
],
cells: [[0, 1, 2]],
};
_this.addGeometry(geometry);
_this.addUniforms("bevel", vertexBevel, props);
};
/** Add miter joins. */
this.miter = function (props) {
var geometry = {
positions: [
[1, 0, 0],
[0, 1, 0],
[0, 0, 1],
[0, 0, 0], // w=1 calculated by vertex shader
],
cells: [
[0, 1, 2],
[0, 2, 3],
],
};
_this.addGeometry(geometry);
_this.addUniforms("miter", vertexMiter, props);
};
/** Add a cap to the line */
this.addCap = function (props, geometry, where) {
if (geometry !== undefined) {
_this.addGeometry(geometry, where);
var u = { dir: { value: where === "Start" ? -1.0 : 1.0 } };
_this.addUniforms("caps", vertexCaps, props, u);
}
};
/** Add a round join */
this.roundJoin = function (props, resolution) {
var roundJoinGeometry = function (resolution) {
var positions = [];
var cells = [];
for (var i = 0; i < resolution + 2; i++)
positions.push([i, 0, 0]);
for (var i = 0; i < resolution; i++)
cells.push([0, i + 1, i + 2]);
return {
positions: positions,
cells: cells,
};
};
_this.addGeometry(roundJoinGeometry(resolution));
var u = { resolution: { value: resolution } };
_this.addUniforms("roundJoin", vertexRoundJoin, props, u);
};
/** Append a geometry to the result */
this.addGeometry = function (geometry, limited) {
_this.data.vertices.push({ index: geometry.cells, limited: limited, position: geometry.positions });
};
/** add a list of shaders with its uniforms */
this.addUniforms = function (uname, vs, props, u) {
// zlevel offset used to stack multiple line attribute, the minimial meaningful value depends on gl shader engine
// may it could be nice to configure this value by user interface in some cases
// if this value is to small, the gl render engine fall into z-fighting
var levelOffset = 0.005;
// split vertex shader imported from file in 2 parts, 1: definition of vars, 2: shader part creating transformed position
var split = vs.split("void main() {");
if (split.length < 2)
throw "shader content unexpected, can't split in 2 parts";
var part1 = split[0];
var part2 = split[1].substring(0, split[1].lastIndexOf("}"));
var sh = props.map(function (e, i) { return _this.sprops(e, uname, part1, part2, u, i * levelOffset); });
_this.data.shader.push(sh);
};
}
/** @internal Access to the created geometry and shader */
Scheme.prototype.getScheme = function () {
return this.data;
};
/** @internal Reset memory, can be used instead creating new instance */
Scheme.prototype.reset = function () {
this.data = {
vertices: [],
shader: [],
};
};
/** create shader uniform */
Scheme.prototype.sprops = function (props, uname, part1, part2, u, zlevel) {
var _a, _b;
var shader = THREE.ShaderLib["standard"];
var uniforms = THREE.UniformsUtils.merge([shader.uniforms]);
return {
uniforms: __assign(__assign(__assign({}, uniforms), { width: { value: (_a = props.width) !== null && _a !== void 0 ? _a : 1 }, ambientLightColor: { value: props.color }, diffuse: { value: props.color }, opacity: { value: (_b = props.opacity) !== null && _b !== void 0 ? _b : 1 }, zlevel: { value: zlevel } }), u),
onBeforeCompile: function (sh) {
sh.vertexShader =
part1 +
sh.vertexShader.replace("#include <beginnormal_vertex>", part2).replace("#include <begin_vertex>", "");
},
customProgramCacheKey: function () { return uname; },
vertexShader: shader.vertexShader,
fragmentShader: shader.fragmentShader,
transparent: this.transparency,
side: zlevel !== undefined && zlevel > 0 ? FrontSide : DoubleSide,
lights: true,
//defines: { STANDARD: "", PHYSICAL: "" },
};
};
return Scheme;
}());
/**
* @internal
* Create a simple mesh geometry consists of two triangles.
* It look like a box or reactangle.
*/
var boxGeometry = function () {
return {
positions: [
[0, -0.5, 0],
[1, -0.5, 0],
[1, 0.5, 0],
[0, 0.5, 0],
],
cells: [
[0, 1, 2],
[0, 2, 3],
],
};
};
/**
* @internal
* Geometry of the square cap
*/
var squareCapGeometry = function () {
return {
positions: [
[0, 0.5, 0],
[0, -0.5, 0],
[0.5, -0.5, 0],
[0.5, 0.5, 0],
],
cells: [
[0, 1, 2],
[0, 2, 3],
],
};
};
/**
* @internal
* Geometry of the top cap
*/
var topCapGeometry = function () {
return {
positions: [
[0, 0.5, 0],
[0, -0.5, 0],
[1, 0, 0],
],
cells: [[0, 1, 2]],
};
};
/**
* @internal
* Geometry of the round cap
*/
var roundCapGeometry = function (resolution) {
var positions = [[0, 0, 0]];
for (var i = 0; i <= resolution; i++) {
var theta = -0.5 * Math.PI + (Math.PI * i) / resolution;
positions.push([0.5 * Math.cos(theta), 0.5 * Math.sin(theta), 0]);
}
var cells = [];
for (var i = 0; i < resolution; i++) {
cells.push([0, i + 1, i + 2]);
}
return { positions: positions, cells: cells };
};
/**
* @public
* Line cap representation.
*/
var CapsList = ["Butt", "Round", "Square", "Top"];
/**
* @public
* Line join representation.
*/
var JoinsList = ["None", "Bevel", "Miter", "Round"];
/** @internal break in pointlist to cancatenate lines to be used as one */
var p0 = [[undefined, undefined, undefined]];
/**
* @public
* Displayed a 2D-Line as indexed mesh, whereby the mesh consists of the determined line options.
* A geometry is calculated which are drawn by several vertex shader.
*
* @example Line between 2 points with default attributes (white, width=1).
* ```
* <Wideline points={[-1, -1, 1, 1]} attr={{}} />
* ```
*
* @example Line between 3 points (red, width=0.2, round join).
* ```
* <Wideline points={[-1, -1, 0, 1, 1, -1]} attr={{ color: "red", width: 0.2 }} join="Round" />
* ```
*/
function Wideline(props) {
var attr = React.useMemo(function () {
return props.attr instanceof Array ? props.attr : [props.attr];
}, [props.attr]);
var transparency = React.useMemo(function () { return (props.opacity !== undefined ? props.opacity < 1 : false); }, [props.opacity]);
var pkey = React.useMemo(function () {
var _a;
return (props.points.length.toString() +
attr.map(function (e) { return "".concat(e.width).concat(e.color).concat(e.offals); }) +
props.join +
props.capsStart +
props.capsEnd +
((_a = props.custom) === null || _a === void 0 ? void 0 : _a.length) +
(props.opacity !== undefined ? props.opacity.toFixed(2) : ""));
}, [props.points, props.join, props.capsStart, props.capsEnd, props.custom, props.opacity, attr]);
// get an array of points (multiple line with one set of attrbibutes)
var aPoints = React.useMemo(function () {
var normalizeShape = function (points) {
var linePoints = [];
if (points[0] instanceof Vector2) {
for (var j = 0; j < points.length; j++) {
var p = points[j];
linePoints.push([p.x, p.y, 0]);
}
}
else if (points[0] instanceof Vector3) {
for (var j = 0; j < points.length; j++) {
var p = points[j];
linePoints.push([p.x, p.y, p.z]);
}
}
else {
var p = points;
for (var j = 0; j < p.length; j += 2) {
linePoints.push([p[j], p[j + 1], 0]);
}
}
return linePoints;
};
// props.points consists of: Shape | Shape[]
if (props.points[0] instanceof Vector3 || typeof props.points[0] === "number")
return normalizeShape(props.points);
return props.points.map(function (p) { return normalizeShape(p).concat(p0); }).flat();
}, [props.points]);
var scheme = React.useMemo(function () { return new Scheme(); }, []);
var geo = React.useMemo(function () {
var _a;
scheme.reset();
var mainColor = function (a) { return new Color(a.color); };
var altColor = function (a) { return (a.offals === undefined ? mainColor(a) : new Color(a.offals)); };
if (props.opacity !== undefined && props.opacity < 1)
scheme.strip(attr.map(function (e) { return ({ color: mainColor(e), width: e.width, opacity: props.opacity }); }));
else
scheme.simple(attr.map(function (e) { return ({ color: mainColor(e), width: e.width }); }));
var capgeo = function (c) {
if (typeof c !== "string")
return c;
switch (c) {
case "Round":
return roundCapGeometry(10);
case "Square":
return squareCapGeometry();
case "Top":
return topCapGeometry();
}
return undefined;
};
if (props.capsStart !== undefined && props.opacity !== 0) {
var s = attr.map(function (e) { return ({ color: altColor(e), width: e.width, opacity: props.opacity }); });
scheme.addCap(s, capgeo(props.capsStart), "Start");
}
if (props.capsEnd !== undefined && props.opacity !== 0) {
var s = attr.map(function (e) { return ({ color: altColor(e), width: e.width, opacity: props.opacity }); });
scheme.addCap(s, capgeo(props.capsEnd), "End");
}
if (props.opacity !== 0) {
switch (props.join) {
case "Bevel": {
var s = attr.map(function (e) { return ({ color: altColor(e), width: e.width, opacity: props.opacity }); });
scheme.bevel(s);
break;
}
case "Miter": {
var s = attr.map(function (e) { return ({ color: altColor(e), width: e.width, opacity: props.opacity }); });
scheme.miter(s);
break;
}
case "Round": {
var s = attr.map(function (e) { return ({ color: altColor(e), width: e.width, opacity: props.opacity }); });
scheme.roundJoin(s, 10);
break;
}
}
}
(_a = props.custom) === null || _a === void 0 ? void 0 : _a.forEach(function (e) { return scheme.custom(e.scheme, e.geometry); });
return scheme.getScheme();
}, [pkey, attr]);
var val = React.useMemo(function () {
scheme.transparency = transparency;
var position = [];
var buildLine = function (points) {
// intermediate points to access current position (pointA) and next position (pointB), etc.
var pointA = [];
var pointB = [];
var pointC = [];
var pointD = [];
var indexAll = [];
var plength = points.length;
/** current index offset */
var ofx = 0;
/** get a point at given index */
var getPoint = function (i) {
return points[Math.min(i, plength - 1)];
};
var _loop_2 = function (ix) {
// prepare next index for current group
var index = [];
var vtx = geo.vertices[ix];
var countPositions = vtx.position.length;
// add only one line part (body, etc.)
var add = function (i) {
if (i < plength - 1) {
position = position.concat(vtx.position);
vtx.index.forEach(function (e) { return index.push([e[0] + ofx, e[1] + ofx, e[2] + ofx]); });
ofx += countPositions;
// add much points as needed
for (var n = 0; n < countPositions; n++) {
pointA.push(getPoint(i));
pointB.push(getPoint(i + 1));
pointC.push(getPoint(i + 2));
pointD.push(getPoint(i + 3));
}
}
};
// loop over all points, add needed line parts (for any segment, or only for start/end)
for (var i = 0; i < plength; i++) {
switch (vtx.limited) {
case "Start": {
var atStart = i === 0 || (i > 0 && getPoint(i - 1)[0] === undefined);
if (atStart)
add(i);
break;
}
case "End": {
var atEnd = i === plength - 2 || (i < plength - 2 && getPoint(i + 2)[0] === undefined);
if (atEnd)
add(i);
break;
}
default:
add(i);
break;
}
}
indexAll.push(index);
};
// loop vertices groups (body, caps, joins, custom)
for (var ix = 0; ix < geo.vertices.length; ix++) {
_loop_2(ix);
}
return {
pA: pointA,
pB: pointB,
pC: pointC,
pD: pointD,
idx: indexAll,
};
};
var line = buildLine(aPoints);
var start = 0;
var gcount = 0;
var cx = [];
var materials = geo.shader;
var idx = line.idx;
if (idx.length !== materials.length)
throw new Error("Vertices vs. Shader count error");
// create material groups in the right order
var groups = [];
var _loop_1 = function (i) {
var index = idx[i];
materials[i].forEach(function (_, seq) { return groups.push({ start: start, count: index.length * 3, materialIndex: gcount++, seq: seq }); });
start += index.length * 3;
cx = cx.concat(index);
};
for (var i = 0; i < idx.length; i++) {
_loop_1(i);
}
// sort by sequence
groups.sort(function (a, b) {
if (a.seq > b.seq)
return 1;
if (a.seq < b.seq)
return -1;
return a.start - b.start;
});
var fa = new Float32Array(line.pA.flat());
var fb = new Float32Array(line.pB.flat());
var fc = new Float32Array(line.pC.flat());
var fd = new Float32Array(line.pD.flat());
return {
anyUpdate: Math.random(),
position: position.flat(),
cx: cx.flat(),
fa: fa,
fb: fb,
fc: fc,
fd: fd,
groups: groups,
materials: materials.flat(),
};
}, [geo, aPoints]);
var mref = React.useRef(null);
var _a = React.useState(undefined), sphere = _a[0], setSphere = _a[1];
var onUpdate = function (geometry) {
var plength = aPoints.length;
var px = new Vector3();
var ax = [];
for (var i = 0; i < plength; i++) {
px.fromArray(aPoints[i], 0);
ax.push(px.clone());
}
var cSphere = function () {
if (mref.current !== null) {
var mesh = mref.current;
geometry.boundingSphere = new Sphere();
geometry.boundingSphere.setFromPoints(ax);
geometry.boundingSphere.applyMatrix4(mesh.matrix);
if (props.boundingSphere) {
var bs = geometry.boundingSphere;
var s = (React.createElement("mesh", { position: bs === null || bs === void 0 ? void 0 : bs.center },
React.createElement("sphereGeometry", { attach: "geometry", args: [bs === null || bs === void 0 ? void 0 : bs.radius, 15, 15] }),
React.createElement("meshStandardMaterial", { attach: "material", color: props.boundingSphere.color, transparent: true, opacity: props.boundingSphere.opacity })));
setSphere(s);
}
}
};
var cBox = function () {
if (mref.current !== null) {
var mesh = mref.current;
geometry.boundingBox = new Box3();
geometry.boundingBox.setFromPoints(ax);
geometry.boundingBox.applyMatrix4(mesh.matrix);
}
};
geometry.computeBoundingSphere = cSphere;
geometry.computeBoundingBox = cBox;
};
var raycast = undefined;
if (props.noRaycast !== true)
raycast = React.useCallback(function (raycaster, intersects) {
if (mref.current !== null) {
var interRay = new Vector3();
var matrixWorld = mref.current.matrixWorld;
var inverseMatrix = new Matrix4();
inverseMatrix.copy(matrixWorld).invert();
var ray = new Ray();
ray.copy(raycaster.ray).applyMatrix4(inverseMatrix);
// fast check bounding sphere first
var geometry = mref.current.geometry;
if (geometry.boundingSphere === null)
geometry.computeBoundingSphere();
if (geometry.boundingSphere !== null && ray.intersectSphere(geometry.boundingSphere, interRay) === null)
return;
var vStart = new Vector3();
var vEnd = new Vector3();
var interSegment = new Vector3();
var width = attr.length > 0 && attr[0].width !== undefined ? attr[0].width : 1;
var plength = aPoints.length;
for (var i = 0; i < plength - 1; i++) {
vStart.fromArray(aPoints[i], 0);
vEnd.fromArray(aPoints[i + 1], 0);
var precision = width / 2;
var precisionSq = precision * precision;
var distSq = ray.distanceSqToSegment(vStart, vEnd, interRay, interSegment);
if (distSq > precisionSq)
continue;
interRay.applyMatrix4(matrixWorld); // Move back to world space for distance calculation
var distance = raycaster.ray.origin.distanceTo(interRay);
if (distance < raycaster.near || distance > raycaster.far)
continue;
intersects.push({
distance: distance,
point: interSegment.clone().applyMatrix4(matrixWorld),
index: i,
face: null,
faceIndex: undefined,
object: mref.current,
});
// break loop on first found intersection
break;
}
}
}, []);
return (React.createElement("group", null,
React.createElement("mesh", __assign({ ref: mref, position: props.position, scale: props.scale, rotation: props.rotation, raycast: raycast }, props.events),
React.createElement("bufferGeometry", { key: val.anyUpdate, attach: "geometry", groups: val.groups, onUpdate: onUpdate },
React.createElement("bufferAttribute", { attach: "attributes-position", count: val.position.length / 3, array: new Float32Array(val.position), itemSize: 3 }),
React.createElement("bufferAttribute", { attach: "index", array: new Uint16Array(val.cx), count: val.cx.length, itemSize: 1 }),
React.createElement("bufferAttribute", { attach: "attributes-pointA", array: val.fa, itemSize: 3 }),
React.createElement("bufferAttribute", { attach: "attributes-pointB", array: val.fb, itemSize: 3 }),
React.createElement("bufferAttribute", { attach: "attributes-pointC", array: val.fc, itemSize: 3 }),
transparency && React.createElement("bufferAttribute", { attach: "attributes-pointD", array: val.fd, itemSize: 3 })),
val.materials.map(function (matProps, i) { return (React.createElement("shaderMaterial", __assign({ key: i + pkey, attach: "material-".concat(i) }, matProps))); })),
sphere));
}
/**
* @public
* Generate a Zig-Zag line.
* First points is left top.
* The last point is right bottom for even count,
* or right top for odd count.
* @param count - How many points the line will have, must \>= 2.
* @param width - The width of the virtual box the line are generated.
* @param height - The height of the virtual box the line are generated.
* @returns The coordinates of the line.
*/
function generatePointsInterleaved(count, width, height) {
count = Math.max(2, count);
var xscale = width ? width * 0.5 : 0.5;
var yscale = height ? height * 0.5 : 0.5;
var stepx = (2 * xscale) / (count - 1);
var y = yscale;
var result = [];
for (var x = 0; x < count; x++) {
result.push(-xscale + x * stepx, y);
y *= -1;
}
return result;
}
/**
* @public
* Display the three-wideline logo as line drawn by itself.
*
* @param props - all possible three js group properties
*/
function Logo(props) {
return (React.createElement("group", __assign({}, props),
React.createElement(Wideline, { points: [-0.5, 0.5, -0.25, -0.5, 0, 0.5, 0.25, -0.5, 0.5, 0.5], attr: [
{ color: "black", width: 0.25 },
{ color: "yellow", width: 0.2 },
{ color: "red", width: 0.15 },
], join: "Round", capsStart: "Round", capsEnd: "Top" })));
}
export { CapsList, JoinsList, Logo, Scheme, Wideline, boxGeometry, generatePointsInterleaved, roundCapGeometry, squareCapGeometry, topCapGeometry };