@mathicsorg/mathics-threejs-backend
Advanced tools
Comparing version 1.2.1 to 1.2.2
CHANGES | ||
======= | ||
1.2.2 | ||
----- | ||
Improvements: | ||
- Type check the code with TypeScript in JSDocs (#103) | ||
- Update jest to version 28 | ||
Bug fixes: | ||
- Fix coplanar polygons | ||
1.2.1 | ||
@@ -5,0 +15,0 @@ ----- |
{ | ||
"name": "@mathicsorg/mathics-threejs-backend", | ||
"version": "1.2.1", | ||
"version": "1.2.2", | ||
"threejs_revision": 138, | ||
@@ -32,3 +32,3 @@ "description": "Mathics 3D Graphics backend using three.js", | ||
"start": "node scripts/server.js", | ||
"test": "(npm start &) && eslint src && jest src && fuser -k 8080/tcp" | ||
"test": "(npm start &) && eslint src && tsc && jest src && fuser -k 8080/tcp" | ||
}, | ||
@@ -42,8 +42,9 @@ "dependencies": { | ||
"eslint-plugin-jest": "^26", | ||
"jest": "^27", | ||
"jest": "^28", | ||
"jest-image-snapshot": "^4", | ||
"jest-puppeteer": "^6", | ||
"minify": "^8", | ||
"rollup": "^2" | ||
"rollup": "^2", | ||
"typescript": "^4" | ||
} | ||
} |
103
src/axes.js
@@ -1,9 +0,14 @@ | ||
import { | ||
Matrix4, | ||
Vector3 | ||
} from '../vendors/three.js'; | ||
// @ts-check | ||
import { Matrix4, Vector3 } from '../vendors/three.js'; | ||
import { scalePartialCoordinate } from './coordinateUtils.js'; | ||
// This changes the value of position. | ||
/** | ||
* Get information about a tick given its position, camera, and container. | ||
* For a better performance, this changes the value of position. | ||
* @param {Vector3} position | ||
* @param {import('../vendors/three.js').Camera} camera | ||
* @param {HTMLElement} container | ||
*/ | ||
function getTickInformation(position, camera, container) { | ||
@@ -33,10 +38,16 @@ position.applyMatrix4( | ||
|| tickPosition[1] < 5 | ||
|| tickPosition[0] > width - 5 | ||
|| tickPosition[1] > height - 5 | ||
|| tickPosition[0] > parseInt(width) - 5 | ||
|| tickPosition[1] > parseInt(height) - 5 | ||
}; | ||
} | ||
// i is 0, 1 or 2. | ||
// This is a 3d coordinate, but as the z value of this function is | ||
// always 0, we can return just the 2 first values. | ||
/** | ||
* Get the tick direction given the radius of the scene. | ||
* i is 0 for x, 1 for y, and 2 for z. | ||
* This is a 3d coordinate, but as the z value of this function is | ||
* always 0, we can return just the 2 first values. | ||
* @param {0 | 1 | 2} i | ||
* @param {number} radius | ||
* @returns [x, y] | ||
*/ | ||
function getTickDirection(i, radius) { | ||
@@ -52,3 +63,20 @@ const tickLength = 0.005 * radius; | ||
export function positionTickNumbers(hasAxes, tickNumbers, ticks, camera, container) { | ||
/** @typedef {import('../vendors/three.js').LineSegments} LineSegments */ | ||
/** | ||
* Re-position the tick numbers after its initial position has been set. | ||
* @todo re-position ticksSmall too. | ||
* @param {[boolean, boolean, boolean]} hasAxes | ||
* @param {[HTMLElement[], HTMLElement[], HTMLElement[]]} tickNumbers | ||
* @param {[LineSegments, LineSegments, LineSegments]} ticks | ||
* @param {import('../vendors/three.js').Camera} camera | ||
* @param {HTMLElement} container | ||
*/ | ||
export function positionTickNumbers( | ||
hasAxes, | ||
tickNumbers, | ||
ticks, | ||
camera, | ||
container | ||
) { | ||
for (let i = 0; i < 3; i++) { | ||
@@ -61,4 +89,6 @@ if (hasAxes[i]) { | ||
new Vector3( | ||
// @ts-expect-error: we are sure this attribute is there | ||
ticks[i].geometry.attributes.position.array[j * 6] * 7 - ticks[i].geometry.attributes.position.array[j * 6 + 3] * 6, | ||
// @ts-expect-error: same as above | ||
ticks[i].geometry.attributes.position.array[j * 6 + 1] * 7 - ticks[i].geometry.attributes.position.array[j * 6 + 4] * 6, | ||
@@ -70,2 +100,3 @@ | ||
// x * 7 - x * 6 = x | ||
// @ts-expect-error: same as above | ||
ticks[i].geometry.attributes.position.array[j * 6 + 2] | ||
@@ -89,2 +120,11 @@ ), | ||
/** | ||
* @param {[boolean, boolean, boolean]} hasAxes | ||
* @param {import('./index.js').ConcreteAxes} axes | ||
* @param {[LineSegments, LineSegments, LineSegments]} ticks | ||
* @param {[LineSegments, LineSegments, LineSegments]} ticksSmall | ||
* @param {{ [index: number]: number }} axesVerticesPosition | ||
* @param {number} radius | ||
* @param {import('./extent.js').Extent} extent | ||
*/ | ||
export function setTicksInitialPosition( | ||
@@ -101,29 +141,48 @@ hasAxes, | ||
if (hasAxes[i]) { | ||
const tickDirection = getTickDirection(i, radius); | ||
const tickDirection = getTickDirection( | ||
/** @type {0 | 1 | 2} */(i), | ||
radius | ||
); | ||
axes.ticks[i][0].forEach((value, j) => { | ||
const partialCoordinate = scalePartialCoordinate(value, i, extent); | ||
const partialCoordinate = scalePartialCoordinate( | ||
value, | ||
/** @type {0 | 1 | 2} */(i), | ||
extent | ||
); | ||
// Initialize the "position" buffer. | ||
// @ts-expect-error: we are sure this attribute is there | ||
ticks[i].geometry.attributes.position.array[j * 6] = axesVerticesPosition[0]; | ||
// @ts-expect-error: same as above | ||
ticks[i].geometry.attributes.position.array[j * 6 + 1] = axesVerticesPosition[1]; | ||
// @ts-expect-error: same as above | ||
ticks[i].geometry.attributes.position.array[j * 6 + 2] = axesVerticesPosition[2]; | ||
// @ts-expect-error: same as above | ||
ticks[i].geometry.attributes.position.array[j * 6 + 3] = axesVerticesPosition[0] + tickDirection[0]; | ||
// @ts-expect-error: same as above | ||
ticks[i].geometry.attributes.position.array[j * 6 + 4] = axesVerticesPosition[1] + tickDirection[1]; | ||
// tickDirection.z is always 0. | ||
// @ts-expect-error: same as above | ||
ticks[i].geometry.attributes.position.array[j * 6 + 5] = axesVerticesPosition[2]; | ||
if (i === 0) { | ||
// @ts-expect-error: same as above | ||
ticks[i].geometry.attributes.position.array[j * 6] = partialCoordinate; | ||
// @ts-expect-error: same as above | ||
ticks[i].geometry.attributes.position.array[j * 6 + 3] = partialCoordinate; | ||
} else if (i === 1) { | ||
// @ts-expect-error: same as above | ||
ticks[i].geometry.attributes.position.array[j * 6 + 1] = partialCoordinate; | ||
// @ts-expect-error: same as above | ||
ticks[i].geometry.attributes.position.array[j * 6 + 4] = partialCoordinate; | ||
} else { | ||
// @ts-expect-error: same as above | ||
ticks[i].geometry.attributes.position.array[j * 6 + 2] = partialCoordinate; | ||
// @ts-expect-error: same as above | ||
ticks[i].geometry.attributes.position.array[j * 6 + 5] = partialCoordinate; | ||
@@ -134,26 +193,42 @@ } | ||
axes.ticks[i][1].forEach((value, j) => { | ||
const partialCoordinate = scalePartialCoordinate(value, i, extent); | ||
const partialCoordinate = scalePartialCoordinate( | ||
value, | ||
/** @type {0 | 1 | 2} */(i), | ||
extent | ||
); | ||
// Initialize the "position" buffer. | ||
// @ts-expect-error: same as above | ||
ticksSmall[i].geometry.attributes.position.array[j * 6] = axesVerticesPosition[0]; | ||
// @ts-expect-error: same as above | ||
ticksSmall[i].geometry.attributes.position.array[j * 6 + 1] = axesVerticesPosition[1]; | ||
// @ts-expect-error: same as above | ||
ticksSmall[i].geometry.attributes.position.array[j * 6 + 2] = axesVerticesPosition[2]; | ||
// @ts-expect-error: same as above | ||
ticksSmall[i].geometry.attributes.position.array[j * 6 + 3] = axesVerticesPosition[0] + tickDirection[0] / 2; | ||
// @ts-expect-error: same as above | ||
ticksSmall[i].geometry.attributes.position.array[j * 6 + 4] = axesVerticesPosition[1] + tickDirection[1] / 2; | ||
// tickDirection.z is always 0. | ||
// @ts-expect-error: same as above | ||
ticksSmall[i].geometry.attributes.position.array[j * 6 + 5] = axesVerticesPosition[2]; | ||
if (i === 0) { | ||
// @ts-expect-error: same as above | ||
ticksSmall[i].geometry.attributes.position.array[j * 6] = partialCoordinate; | ||
// @ts-expect-error: same as above | ||
ticksSmall[i].geometry.attributes.position.array[j * 6 + 3] = partialCoordinate; | ||
} else if (i === 1) { | ||
// @ts-expect-error: same as above | ||
ticksSmall[i].geometry.attributes.position.array[j * 6 + 1] = partialCoordinate; | ||
// @ts-expect-error: same as above | ||
ticksSmall[i].geometry.attributes.position.array[j * 6 + 4] = partialCoordinate; | ||
} else { | ||
// @ts-expect-error: same as above | ||
ticksSmall[i].geometry.attributes.position.array[j * 6 + 2] = partialCoordinate; | ||
// @ts-expect-error: same as above | ||
ticksSmall[i].geometry.attributes.position.array[j * 6 + 5] = partialCoordinate; | ||
@@ -160,0 +235,0 @@ } |
@@ -0,9 +1,20 @@ | ||
// @ts-check | ||
import { scaleCoordinate } from './coordinateUtils.js'; | ||
// This is usually used to copy a coordinate into a coordinate buffer. | ||
// The coordinates buffers are preallocated for efficiency on GPUs. | ||
// Also, WebGL only accepts a typed array as an attribute. | ||
// array is a 3-elements array | ||
// index is the element index, the biggest index in a 3n-elements buffer is | ||
// n - 1 (the first element is 0) | ||
/** @typedef {import('./coordinateUtils.js').Coordinate} Coordinate */ | ||
/** | ||
* Copy a 3-element array into a buffer given an index. | ||
* The 1st element will go into index * 3. | ||
* The 2nd element will go into index * 3 + 1. | ||
* The 3rd element will go into index * 3 + 2. | ||
* This is usually used to copy a coordinate into a coordinate buffer. | ||
* The coordinates buffers are preallocated for efficiency on GPUs. | ||
* Also, WebGL only accepts a typed array as an attribute. | ||
* | ||
* @param {{ [index: number]: number }} buffer | ||
* @param {import('./coordinateUtils.js').Coordinate} array | ||
* @param {number} index | ||
*/ | ||
export function copyArray3IntoBuffer(buffer, array, index) { | ||
@@ -15,4 +26,13 @@ buffer[index * 3] = array[0]; | ||
// The same as copyArray3IntoBuffer, but with | ||
// a Vector3 instead of a 3-element array. | ||
/** | ||
* Copy a Vector3 into a buffer given an index. | ||
* The x value will go into index * 3. | ||
* The y value will go into index * 3 + 1. | ||
* The z value will go into index * 3 + 2. | ||
* The same as {@link copyArray3IntoBuffer}, but with | ||
* a Vector3 instead of a 3-element array. | ||
* @param {{ [index: number]: number }} buffer | ||
* @param {import('../vendors/three.js').Vector3} vector | ||
* @param {number} index | ||
*/ | ||
export function copyVector3IntoBuffer(buffer, vector, index) { | ||
@@ -24,9 +44,18 @@ buffer[index * 3] = vector.x; | ||
// Create a coordinate buffer and copy the coordinates from coords to it. | ||
// The coordinates are in the form [[x, y, z]] or [null, [x, y, z]] and copyIntoCoordinateBuffer receives a list, so we need to: | ||
// - transform [null, [x, y, z]] into [x, y, z]. This is done through scaleCoordinate. | ||
// - transform [[x, y, z]] into [x, y, z]. This is done taking the first element of the list. | ||
/** | ||
* Create a coordinate buffer and copy the coordinates from coords to it. | ||
* @param {Array<[Coordinate, null] | [null, Coordinate]>} coords | ||
* @param {import('./extent.js').Extent} extent | ||
* @returns a coordinate buffer populated with the coordinates from coords | ||
*/ | ||
export function getPopulatedCoordinateBuffer(coords, extent) { | ||
const coordinateBuffer = new Float32Array(coords.length * 3); | ||
// The coordinates are in the form [[x, y, z]] or [null, [x, y, z]] | ||
// and copyIntoCoordinateBuffer receives a list, so we need to: | ||
// - transform [null, [x, y, z]] into [x, y, z]. | ||
// This is done through scaleCoordinate. | ||
// - transform [[x, y, z]] into [x, y, z]. | ||
// This is done taking the first element of the list. | ||
coords.forEach((coordinate, i) => | ||
@@ -43,5 +72,11 @@ copyArray3IntoBuffer( | ||
// Create 2 coordinate buffers and copy the even-numbered coordinates from coords to the 1st coordinate buffer and the odd-numbered ones to the 2nd. | ||
// This is usuful when the primitive have a begin and a end coordinate. Both can't be in the same BufferAttribute. | ||
// Returns an array with the 2 coordinate buffers | ||
/** | ||
* Create 2 coordinate buffers and copy the even-numbered coordinates from | ||
* coords to the 1st coordinate buffer and the odd-numbered ones to the 2nd. | ||
* This is usuful when the primitive have a begin and a end coordinate. | ||
* Both can't be in the same BufferAttribute. | ||
* @param {Array<[Coordinate, null] | [null, Coordinate]>} coords | ||
* @param {import('./extent.js').Extent} extent | ||
* @returns an array with the 2 coordinate buffers | ||
*/ | ||
export function get2PopulatedCoordinateBuffers(coords, extent) { | ||
@@ -55,3 +90,6 @@ // number of vertices per coordinate / number of coordinates per primitive = 3 / 2 | ||
coordinateBuffer1, | ||
coords[i * 2][0] ?? scaleCoordinate(coords[i * 2][1], extent), | ||
coords[i * 2][0] ?? scaleCoordinate( | ||
/** @type {Coordinate} */(coords[i * 2][1]), | ||
extent | ||
), | ||
i | ||
@@ -62,3 +100,6 @@ ); | ||
coordinateBuffer2, | ||
coords[i * 2 + 1][0] ?? scaleCoordinate(coords[i * 2 + 1][1], extent), | ||
coords[i * 2 + 1][0] ?? scaleCoordinate( | ||
/** @type {Coordinate} */(coords[i * 2 + 1][1]), | ||
extent | ||
), | ||
i | ||
@@ -65,0 +106,0 @@ ); |
@@ -0,1 +1,14 @@ | ||
// @ts-check | ||
/** @typedef {[number, number, number]} Coordinate */ | ||
/** | ||
* Scale a coordinate (usually the 2nd element of a higher-order coordinate) | ||
* to the size of the bounding box. e.g.: | ||
* (0, 0, 0) is the lower-front-left of the bounding box. | ||
* (1, 1, 1) is the upper-back-right of the bounding box. | ||
* @param {Coordinate} coordinate | ||
* @param {import('./extent').Extent} extent | ||
* @returns {Coordinate} | ||
*/ | ||
export function scaleCoordinate(coordinate, extent) { | ||
@@ -9,5 +22,9 @@ return [ | ||
// Multiply partialCoordinate by (extent.`i`max - extent.`i`min) and add | ||
// extent.`i`min. | ||
// i is 0 for x, 1 for y and 2 for z. | ||
/** | ||
* i is 0 for x, 1 for y and 2 for z. | ||
* @param {number} partialCoordinate | ||
* @param {0 | 1 | 2} i | ||
* @param {import('./extent').Extent} extent | ||
* @returns partialCoordinate * (extent.imax - extent.imin) + extent.imin | ||
*/ | ||
export function scalePartialCoordinate(partialCoordinate, i, extent) { | ||
@@ -14,0 +31,0 @@ if (i === 0) { |
@@ -0,1 +1,3 @@ | ||
// @ts-check | ||
import { | ||
@@ -11,3 +13,9 @@ Matrix4, | ||
// Modified from three.js CatmullRomCurve3 | ||
/** @typedef {import('./coordinateUtils.js').Coordinate} Coordinate */ | ||
/** | ||
* Modified from three.js CatmullRomCurve3 | ||
* @param {Array<[Coordinate, null] | [null, Coordinate]>} coordinates | ||
* @param {import('./extent.js').Extent} extent | ||
*/ | ||
export function getCentripetalCurve(coordinates, extent) { | ||
@@ -30,4 +38,7 @@ const points = coordinates.map((coordinate) => new Vector3( | ||
/** | ||
* @param {number} t | ||
*/ | ||
function getPoint(t) { | ||
const px = new CubicPoly(), py = new CubicPoly(), pz = new CubicPoly(); | ||
const px = CubicPoly(), py = CubicPoly(), pz = CubicPoly(); | ||
@@ -89,6 +100,9 @@ const p = (points.length - 1) * t; | ||
// Returns a unit vector tangent at t. | ||
// In case any sub curve does not implement its tangent derivation, | ||
// 2 points a small delta apart will be used to find its gradient | ||
// which seems to give a reasonable approximation. | ||
/** | ||
* In case any sub curve does not implement its tangent derivation, | ||
* 2 points a small delta apart will be used to find its gradient | ||
* which seems to give a reasonable approximation. | ||
* @param {number} t | ||
* @returns a unit vector tangent at t | ||
*/ | ||
function getTangent(t) { | ||
@@ -109,2 +123,5 @@ const delta = 0.0001; | ||
/** | ||
* @param {number} u | ||
*/ | ||
function getUtoTmapping(u) { | ||
@@ -156,5 +173,11 @@ let i = 0; | ||
getPoint, | ||
/** | ||
* @param {number} u | ||
*/ | ||
getPointAt(u) { | ||
return getPoint(getUtoTmapping(u)); | ||
}, | ||
/** | ||
* @param {number} segments | ||
*/ | ||
computeFrenetFrames(segments) { | ||
@@ -161,0 +184,0 @@ const vector = new Vector3(); |
@@ -0,4 +1,14 @@ | ||
// @ts-check | ||
import { scaleCoordinate } from './coordinateUtils.js'; | ||
export default function (elements) { | ||
/** @typedef {import('./coordinateUtils.js').Coordinate} Coordinate */ | ||
/** @typedef {ReturnType<extent>} Extent */ | ||
/** | ||
* Get the extent (bounding box size) for the elements | ||
* @param {import('./primitives/index.js').PrimitiveElement[]} elements | ||
*/ | ||
export default function extent(elements) { | ||
const extent = { | ||
@@ -119,23 +129,62 @@ xmin: 0, | ||
if (!coordinate[0]) { | ||
coordinate[0] = scaleCoordinate(coordinate[1], extent); | ||
// We are changing the type of coordinate[0] from null | ||
// to Coordinate, TypeScript gives a warning here if we | ||
// don't cast coordinate[0] to unknown. | ||
/** @type {unknown} */(coordinate[0]) = scaleCoordinate(coordinate[1], extent); | ||
if (coordinate[0][0] - radius < extent.xmin) { | ||
extent.xmin = coordinate[0][0] - radius; | ||
if ( | ||
/** @type {Coordinate} */( | ||
/** @type {unknown} */(coordinate[0]) | ||
)[0] - radius < extent.xmin | ||
) { | ||
extent.xmin = /** @type {Coordinate} */( | ||
/** @type {unknown} */(coordinate[0]) | ||
)[0] - radius; | ||
} | ||
if (coordinate[0][0] + radius > extent.xmax) { | ||
extent.xmax = coordinate[0][0] + radius; | ||
if ( | ||
/** @type {Coordinate} */( | ||
/** @type {unknown} */(coordinate[0]) | ||
)[0] + radius > extent.xmax | ||
) { | ||
extent.xmax = /** @type {Coordinate} */( | ||
/** @type {unknown} */(coordinate[0]) | ||
)[0] + radius; | ||
} | ||
if (coordinate[0][1] - radius < extent.ymin) { | ||
extent.ymin = coordinate[0][1] - radius; | ||
if ( | ||
/** @type {Coordinate} */( | ||
/** @type {unknown} */(coordinate[0]) | ||
)[1] - radius < extent.ymin | ||
) { | ||
extent.ymin = /** @type {Coordinate} */( | ||
/** @type {unknown} */(coordinate[0]) | ||
)[1] - radius; | ||
} | ||
if (coordinate[0][1] + radius > extent.ymax) { | ||
extent.ymax = coordinate[0][1] + radius; | ||
if ( | ||
/** @type {Coordinate} */( | ||
/** @type {unknown} */(coordinate[0]) | ||
)[1] + radius > extent.ymax | ||
) { | ||
extent.ymax = /** @type {Coordinate} */( | ||
/** @type {unknown} */(coordinate[0]) | ||
)[1] + radius; | ||
} | ||
if (coordinate[0][2] - radius < extent.zmin) { | ||
extent.zmin = coordinate[0][2] - radius; | ||
if ( | ||
/** @type {Coordinate} */( | ||
/** @type {unknown} */(coordinate[0]) | ||
)[2] - radius < extent.zmin | ||
) { | ||
extent.zmin = /** @type {Coordinate} */( | ||
/** @type {unknown} */(coordinate[0]) | ||
)[2] - radius; | ||
} | ||
if (coordinate[0][2] + radius > extent.zmax) { | ||
extent.zmax = coordinate[0][2] + radius; | ||
if ( | ||
/** @type {Coordinate} */( | ||
/** @type {unknown} */(coordinate[0]) | ||
)[2] + radius > extent.zmax | ||
) { | ||
extent.zmax = /** @type {Coordinate} */( | ||
/** @type {unknown} */(coordinate[0]) | ||
)[2] + radius; | ||
} | ||
@@ -142,0 +191,0 @@ } |
@@ -0,1 +1,3 @@ | ||
// @ts-check | ||
import { | ||
@@ -10,3 +12,7 @@ BufferAttribute, | ||
// Modified from three.js' BufferGeometryUtils. | ||
/** | ||
* Modified from three.js' BufferGeometryUtils. | ||
* @param {BufferAttribute[]} attributes | ||
* @returns a new attribute with the values from all attributes. | ||
*/ | ||
function mergeBufferAttributes(attributes) { | ||
@@ -30,5 +36,9 @@ let arrayLength = 0; | ||
// Modified from three.js' BufferGeometryUtils. | ||
/** | ||
* Modified from three.js' BufferGeometryUtils. | ||
* @param {BufferGeometry[]} geometries | ||
*/ | ||
export function mergeBufferGeometries(geometries) { | ||
const mergedIndex = []; | ||
/** @type {{ [key: string]: BufferAttribute[] }} */ | ||
const attributes = {}; | ||
@@ -41,2 +51,4 @@ const mergedGeometry = new BufferGeometry(); | ||
// @ts-expect-error: name is in attributes, so we can use it as an | ||
// index. | ||
attributes[name].push(geometries[i].attributes[name]); | ||
@@ -49,2 +61,4 @@ } | ||
// @ts-expect-error: we expect the geometry to have the | ||
// position attribute. | ||
indexOffset += geometries[i].attributes.position.count; | ||
@@ -65,3 +79,8 @@ } | ||
// This is used in spheres and in tubes' end caps. | ||
/** | ||
* This is used in spheres and in tubes' end caps. | ||
* @param {number} radius | ||
* @param {boolean} instanced | ||
* @param {boolean} halfSphere | ||
*/ | ||
export function getSphereGeometry(radius, instanced = false, halfSphere = false) { | ||
@@ -136,2 +155,7 @@ const phiEnd = halfSphere ? Math.PI : Math.PI * 2; | ||
// TODO: add cache | ||
/** | ||
* @param {number} radius | ||
* @param {ReturnType<import('curve.js').getCentripetalCurve>} path | ||
* @returns {BufferGeometry} the geometry of a tube passing through the path. | ||
*/ | ||
export function getTubeGeometry(radius, path) { | ||
@@ -138,0 +162,0 @@ // tubularSegments |
@@ -0,1 +1,3 @@ | ||
// @ts-check | ||
import { | ||
@@ -17,3 +19,15 @@ BufferAttribute, | ||
import { getUniformsBuffer } from './uniforms.js'; | ||
import { clamp } from './math.js'; | ||
/** | ||
* @typedef {import('./index.js').AxesTicks} AxesTicks | ||
* @typedef {import('./index.js').Axes} Axes | ||
* @typedef {import('./index.js').ConcreteAxes} ConcreteAxes | ||
* @typedef {import('./index.js').TicksStyle} TicksStyle | ||
*/ | ||
/** | ||
* Set the default CSS for the container | ||
* @param {HTMLElement} container | ||
*/ | ||
function setDefaultContainerStyle(container) { | ||
@@ -41,2 +55,14 @@ const style = getComputedStyle(container); | ||
/** | ||
* Plot the data | ||
* @param {HTMLElement} container | ||
* @param {{ | ||
* axes?: Axes, | ||
* autoRescale?: boolean, | ||
* extent?: import('./extent.js').Extent, | ||
* elements?: import('./primitives/index.js').PrimitiveElement[], | ||
* lighting?: { type: import('./lights.js').LightType }[], | ||
* viewpoint?: import('./coordinateUtils.js').Coordinate | ||
* }} data | ||
*/ | ||
export default function ( | ||
@@ -56,13 +82,13 @@ container, | ||
let isCtrlDown, | ||
isShiftDown, | ||
onMouseDownFocus, | ||
onCtrlDownFov, | ||
hasAxes, | ||
isMouseDown = false, | ||
theta, | ||
onMouseDownTheta, | ||
phi, | ||
onMouseDownPhi, | ||
onTouchStartFingersDistance; | ||
let /** @type {boolean} */ isCtrlDown, | ||
/** @type {boolean} */ isShiftDown, | ||
/** @type {Vector3} */ onMouseDownFocus, | ||
/** @type {number} */ onCtrlDownFov, | ||
/** @type {[boolean, boolean, boolean]} */ hasAxes, | ||
/** @type {boolean} */ isMouseDown = false, | ||
/** @type {number} */ theta, | ||
/** @type {number} */ onMouseDownTheta, | ||
/** @type {number} */ phi, | ||
/** @type {number} */ onMouseDownPhi, | ||
/** @type {number} */ onTouchStartFingersDistance; | ||
@@ -107,2 +133,3 @@ const onMouseDownPosition = new Int16Array(2); | ||
function updateCameraPosition() { | ||
// @ts-expect-error: bad three.js typing | ||
camera.position.set( | ||
@@ -185,3 +212,4 @@ radius * Math.sin(theta) * Math.cos(phi), | ||
// axes ticks | ||
const ticks = new Array(3), ticksSmall = new Array(3); | ||
const ticks = /** @type {[LineSegments, LineSegments, LineSegments]} */(new Array(3)), | ||
ticksSmall = /** @type {[LineSegments, LineSegments, LineSegments]} */(new Array(3)); | ||
@@ -194,3 +222,5 @@ for (let i = 0; i < 3; i++) { | ||
new BufferAttribute( | ||
new Float32Array(6 * axes.ticks[i][0].length), | ||
new Float32Array( | ||
6 * /** @type {AxesTicks} */(axes.ticks)[i][0].length | ||
), | ||
3 | ||
@@ -208,3 +238,5 @@ ) | ||
new BufferAttribute( | ||
new Float32Array(6 * axes.ticks[i][1].length), | ||
new Float32Array( | ||
6 * /** @type {AxesTicks} */(axes.ticks)[i][1].length | ||
), | ||
3 | ||
@@ -222,5 +254,6 @@ ) | ||
hasAxes, | ||
axes, | ||
/** @type {ConcreteAxes} */(axes), | ||
ticks, | ||
ticksSmall, | ||
// @ts-expect-error: we are sure this attribute is in there | ||
boundingBox.geometry.attributes.position.array, | ||
@@ -231,8 +264,9 @@ radius, | ||
// axes numbering using divs | ||
const tickNumbers = new Array(3); | ||
const tickNumbers = /** @type {[HTMLElement[], HTMLElement[], HTMLElement[]]} */(new Array(3)); | ||
for (let i = 0; i < 3; i++) { | ||
if (hasAxes[i]) { | ||
tickNumbers[i] = new Array(axes.ticks[i][0].length); | ||
tickNumbers[i] = new Array( | ||
/** @type {AxesTicks} */(axes.ticks)[i][0].length | ||
); | ||
@@ -242,16 +276,22 @@ for (let j = 0; j < tickNumbers[i].length; j++) { | ||
if (i < axes.ticks_style?.length) { | ||
color = `rgb(${axes.ticks_style[i][0] * 255}, ${axes.ticks_style[i][1] * 255}, ${axes.ticks_style[i][2] * 255})`; | ||
if (i < (axes.ticks_style?.length ?? 0)) { | ||
color = `rgb( | ||
${/** @type {TicksStyle} */(axes.ticks_style)[i][0] * 255}, | ||
${/** @type {TicksStyle} */(axes.ticks_style)[i][1] * 255}, | ||
${/** @type {TicksStyle} */(axes.ticks_style)[i][2] * 255} | ||
)`; | ||
} | ||
tickNumbers[i][j] = document.createElement('div'); | ||
tickNumbers[i][j].innerHTML = axes.ticks[i][2][j].startsWith('0.') | ||
? axes.ticks[i][2][j].replace('0.', '.') | ||
: axes.ticks[i][2][j]; | ||
tickNumbers[i][j].innerHTML = | ||
/** @type {AxesTicks} */(axes.ticks)[i][2][j] | ||
.startsWith('0.') | ||
? /** @type {AxesTicks} */(axes.ticks)[i][2][j].replace('0.', '.') | ||
: /** @type {AxesTicks} */(axes.ticks)[i][2][j]; | ||
// handle minus signs | ||
if (axes.ticks[i][0][j] >= 0) { | ||
if (/** @type {AxesTicks} */(axes.ticks)[i][0][j] >= 0) { | ||
tickNumbers[i][j].style.paddingLeft = '0.5em'; | ||
} else { | ||
tickNumbers[i][j].style.paddingLeft = 0; | ||
tickNumbers[i][j].style.paddingLeft = '0px'; | ||
} | ||
@@ -270,3 +310,8 @@ | ||
elements.forEach( | ||
(element) => scene.add(primitiveFunctions[element.type](element, uniforms, extent, container)) | ||
(element) => scene.add(primitiveFunctions[element.type]( | ||
element, | ||
uniforms, | ||
/** @type {import('./extent.js').Extent} */(extent), | ||
container | ||
)) | ||
); | ||
@@ -312,4 +357,7 @@ | ||
proj2d.set( | ||
// @ts-expect-error: we are sure this attribute is in there | ||
boundingBox.geometry.attributes.position.array[i * 3], | ||
// @ts-expect-error: the same as above | ||
boundingBox.geometry.attributes.position.array[i * 3 + 1], | ||
// @ts-expect-error: the same as above | ||
boundingBox.geometry.attributes.position.array[i * 3 + 2] | ||
@@ -328,2 +376,6 @@ ).applyMatrix4(camera.matrixWorldInverse); | ||
/** | ||
* @param {MouseEvent | TouchEvent} event | ||
* @param {boolean} touch whether the event is with touch instead of mouse. | ||
*/ | ||
function onMouseDown(event, touch) { | ||
@@ -339,4 +391,8 @@ event.preventDefault(); | ||
onMouseDownPosition[0] = touch ? event.touches[0].clientX : event.clientX; | ||
onMouseDownPosition[1] = touch ? event.touches[0].clientY : event.clientY; | ||
onMouseDownPosition[0] = touch | ||
? /** @type {TouchEvent} */(event).touches[0].clientX | ||
: /** @type {MouseEvent} */(event).clientX; | ||
onMouseDownPosition[1] = touch | ||
? /** @type {TouchEvent} */(event).touches[0].clientY | ||
: /** @type {MouseEvent} */(event).clientY; | ||
@@ -346,2 +402,6 @@ onMouseDownFocus = focus.clone(); | ||
/** | ||
* @param {MouseEvent | TouchEvent} event | ||
* @param {boolean} touch whether the event is with touch instead of mouse. | ||
*/ | ||
function onMouseMove(event, touch) { | ||
@@ -352,4 +412,8 @@ event.preventDefault(); | ||
const clientX = touch ? event.touches[0].clientX : event.clientX; | ||
const clientY = touch ? event.touches[0].clientY : event.clientY; | ||
const clientX = touch | ||
? /** @type {TouchEvent} */(event).touches[0].clientX | ||
: /** @type {MouseEvent} */(event).clientX; | ||
const clientY = touch | ||
? /** @type {TouchEvent} */(event).touches[0].clientY | ||
: /** @type {MouseEvent} */(event).clientY; | ||
@@ -374,2 +438,3 @@ positionTickNumbers(hasAxes, tickNumbers, ticks, camera, container); | ||
const cameraY = new Vector3() | ||
// @ts-expect-error: bad three.js typing | ||
.subVectors(focus, camera.position) | ||
@@ -386,3 +451,6 @@ .normalize() | ||
updateCameraPosition(); | ||
} else if (event.ctrlKey || (touch && event.touches.length === 2)) { // zoom | ||
} else if ( | ||
event.ctrlKey | ||
|| (touch && /** @type {TouchEvent} */(event).touches.length === 2) | ||
) { // zoom | ||
if (!isCtrlDown) { | ||
@@ -398,4 +466,4 @@ isCtrlDown = true; | ||
onTouchStartFingersDistance = Math.hypot( | ||
clientX - event.touches[1].clientX, | ||
clientY - event.touches[1].clientY | ||
clientX - /** @type {TouchEvent} */(event).touches[1].clientX, | ||
clientY - /** @type {TouchEvent} */(event).touches[1].clientY | ||
); | ||
@@ -409,4 +477,4 @@ } | ||
fov -= (Math.hypot( | ||
clientX - event.touches[1].clientX, | ||
clientY - event.touches[1].clientY | ||
clientX - /** @type {TouchEvent} */(event).touches[1].clientX, | ||
clientY - /** @type {TouchEvent} */(event).touches[1].clientY | ||
) - onTouchStartFingersDistance) / 25; | ||
@@ -433,5 +501,9 @@ } else { | ||
phi = onMouseDownPhi + 2 * Math.PI * (onMouseDownPosition[0] - event.clientX) / parseInt(width); | ||
phi = onMouseDownPhi + 2 * Math.PI * ( | ||
onMouseDownPosition[0] - /** @type {MouseEvent} */(event).clientX | ||
) / parseInt(width); | ||
theta = onMouseDownTheta + 2 * Math.PI * (onMouseDownPosition[1] - event.clientY) / parseInt(height); | ||
theta = onMouseDownTheta + 2 * Math.PI * ( | ||
onMouseDownPosition[1] - /** @type {MouseEvent} */(event).clientY | ||
) / parseInt(height); | ||
@@ -441,6 +513,3 @@ // This prevents spinning over the poles by keeping the angle | ||
// Angles too close to 0 or Pi problems. | ||
theta = Math.max( | ||
Math.min(theta, Math.PI - 1e-12), | ||
1e-12 | ||
); | ||
theta = clamp(theta, 1e-12, Math.PI - 1e-12); | ||
@@ -453,2 +522,5 @@ updateCameraPosition(); | ||
/** | ||
* @param {MouseEvent | TouchEvent} event | ||
*/ | ||
function onMouseUp(event) { | ||
@@ -455,0 +527,0 @@ event.preventDefault(); |
@@ -0,20 +1,54 @@ | ||
// @ts-check | ||
import drawGraphics3d from './graphics3d.js'; | ||
function translationLayer(container, object, maxSize, innerWidthMultiplier) { | ||
if (object.protocol) { | ||
/** | ||
* @typedef {[number[], number[], string[]]} AxesTick | ||
* @typedef {[AxesTick, AxesTick, AxesTick]} AxesTicks | ||
* @typedef {[number, number, number]} TickStyle | ||
* @typedef {[TickStyle, TickStyle, TickStyle]} TicksStyle | ||
* @typedef {{ | ||
* hasaxes?: boolean | [boolean, boolean, boolean], | ||
* ticks?: AxesTicks, | ||
* ticks_style?: TicksStyle | ||
* }} Axes | ||
* @typedef {{ | ||
* hasaxes: boolean | [boolean, boolean, boolean], | ||
* ticks: AxesTicks, | ||
* ticks_style?: TicksStyle | ||
* }} ConcreteAxes | ||
*/ | ||
/** | ||
* Translate deprecated code into running code. | ||
* @param {HTMLElement} container | ||
* @param {{ | ||
* axes?: Axes, | ||
* autoRescale?: boolean, | ||
* extent?: import('./extent.js').Extent, | ||
* elements?: any[], | ||
* lighting?: any[], | ||
* protocol?: string, | ||
* viewpoint?: import('./coordinateUtils.js').Coordinate | ||
* }} data data | ||
* @param {number} maxSize | ||
* @param {number} innerWidthMultiplier | ||
*/ | ||
function translationLayer(container, data, maxSize, innerWidthMultiplier) { | ||
if (data.protocol) { | ||
// protocol version is X.Y, so it is an array of two elements: major version and minor version | ||
const versionArray = object.protocol.match(/\d/g); | ||
const versionArray = data.protocol.match(/\d/g); | ||
if (parseInt(versionArray[0]) !== 1) { | ||
if (parseInt(/** @type {RegExpMatchArray} */(versionArray)[0]) !== 1) { | ||
const warning = document.createElement('p'); | ||
warning.style.color = 'yellow'; | ||
warning.innerText = `The major revision version of mathics-threejs-backend is 1, but it was expected to be ${versionArray[0]}. Trying to draw the graphics.`; | ||
warning.innerText = `The major revision version of mathics-threejs-backend is 1, but it was expected to be ${/** @type {RegExpMatchArray} */(versionArray)[0]}. Trying to draw the graphics.`; | ||
container.appendChild(warning); | ||
} else if (parseInt(versionArray[1]) > 2) { | ||
} else if (parseInt(/** @type {RegExpMatchArray} */(versionArray)[1]) > 2) { | ||
const warning = document.createElement('p'); | ||
warning.style.color = 'yellow'; | ||
warning.innerText = `The minor revision version of mathics-threejs-backend is 1, but it was expected to be at least ${versionArray[1]}. Trying to draw the graphics.`; | ||
warning.innerText = `The minor revision version of mathics-threejs-backend is 1, but it was expected to be at least ${/** @type {RegExpMatchArray} */(versionArray)[1]}. Trying to draw the graphics.`; | ||
@@ -25,3 +59,3 @@ container.appendChild(warning); | ||
object.elements?.forEach((primitive) => { | ||
data.elements?.forEach((/** @type {*} */primitive) => { | ||
if (primitive.faceColor) { | ||
@@ -34,3 +68,3 @@ primitive.color = primitive.faceColor; | ||
object.lighting?.forEach((light) => { | ||
data.lighting?.forEach((/** @type {*} */light) => { | ||
if (light.position) { | ||
@@ -48,7 +82,8 @@ light.coords = [light.position]; | ||
return drawGraphics3d(container, object); | ||
return drawGraphics3d(container, data); | ||
} | ||
// @ts-expect-error: drawGraphics3d ins't a property of window. | ||
window.drawGraphics3d = translationLayer; | ||
export default drawGraphics3d; |
@@ -0,1 +1,3 @@ | ||
// @ts-check | ||
// Unlike the primitives, all lights are in the same file. | ||
@@ -22,13 +24,30 @@ // Each light function takes 2 parameters and returns a three.js object. | ||
/** @typedef {import('./coordinateUtils.js').Coordinate} Coordinate */ | ||
/** @typedef {'ambient' | 'directional' | 'point' | 'spot'} LightType */ | ||
/** @type {{ [key in LightType]: Function }} */ | ||
export default { | ||
// See https://mathics3.github.io/mathics-threejs-backend/lights/ambient | ||
// for the high-level description of what is being rendered. | ||
ambient: ({ color = [1, 1, 1] }, lights) => { | ||
lights.ambientLightColor.value[0] += color[0]; | ||
lights.ambientLightColor.value[1] += color[1]; | ||
lights.ambientLightColor.value[2] += color[2]; | ||
/** | ||
* See {@link https://mathics3.github.io/mathics-threejs-backend/lights/ambient} | ||
* for the high-level description of what is being rendered. | ||
* @param {{ color: [number, number, number] }} light | ||
* @param {import('./uniforms.js').UniformsBuffer} uniforms | ||
*/ | ||
ambient: ({ color = [1, 1, 1] }, uniforms) => { | ||
uniforms.ambientLightColor.value[0] += color[0]; | ||
uniforms.ambientLightColor.value[1] += color[1]; | ||
uniforms.ambientLightColor.value[2] += color[2]; | ||
}, | ||
// See https://mathics3.github.io/mathics-threejs-backend/lights/directional | ||
// for the high-level description of what is being rendered. | ||
directional: ({ color = [1, 1, 1], coords }, lights, extent) => { | ||
/** | ||
* See {@link https://mathics3.github.io/mathics-threejs-backend/lights/directional} | ||
* for the high-level description of what is being rendered. | ||
* @param {{ | ||
* color: [number, number, number], | ||
* coords: [Coordinate, null] | [null, Coordinate] | ||
* }} light | ||
* @param {import('./uniforms.js').UniformsBuffer} uniforms | ||
* @param {import('./extent.js').Extent} extent | ||
*/ | ||
directional: ({ color = [1, 1, 1], coords }, uniforms, extent) => { | ||
const direction = new Vector3( | ||
@@ -42,3 +61,3 @@ ...(coords[0] ?? scaleCoordinate(coords[1], extent)) | ||
lights.directionalLights.value.push({ | ||
uniforms.directionalLights.value.push({ | ||
color, | ||
@@ -48,6 +67,14 @@ direction | ||
}, | ||
// See https://mathics3.github.io/mathics-threejs-backend/lights/point | ||
// for the high-level description of what is being rendered. | ||
point: ({ color = [1, 1, 1], coords }, lights, extent) => { | ||
lights.pointLights.value.push({ | ||
/** | ||
* See {@link https://mathics3.github.io/mathics-threejs-backend/lights/point} | ||
* for the high-level description of what is being rendered. | ||
* @param {{ | ||
* color: [number, number, number], | ||
* coords: [Coordinate, null] | [null, Coordinate] | ||
* }} light | ||
* @param {import('./uniforms.js').UniformsBuffer} uniforms | ||
* @param {import('./extent.js').Extent} extent | ||
*/ | ||
point: ({ color = [1, 1, 1], coords }, uniforms, extent) => { | ||
uniforms.pointLights.value.push({ | ||
color, | ||
@@ -57,6 +84,16 @@ basePosition: new Vector3(...coords[0] ?? scaleCoordinate(coords[1], extent)) | ||
}, | ||
// See https://mathics3.github.io/mathics-threejs-backend/lights/spot | ||
// for the high-level description of what is being rendered. | ||
// The default angle is π/2. | ||
spot: ({ angle = 1.57079632679, color = [1, 1, 1], coords, target }, lights, extent) => { | ||
/** | ||
* See {@link https://mathics3.github.io/mathics-threejs-backend/lights/spot} | ||
* for the high-level description of what is being rendered. | ||
* The default angle is π/2. | ||
* @param {{ | ||
* angle: number, | ||
* color: [number, number, number], | ||
* coords: [Coordinate, null] | [null, Coordinate], | ||
* target: [Coordinate, null] | [null, Coordinate] | ||
* }} light | ||
* @param {import('./uniforms.js').UniformsBuffer} uniforms | ||
* @param {import('./extent.js').Extent} extent | ||
*/ | ||
spot: ({ angle = 1.57079632679, color = [1, 1, 1], coords, target }, uniforms, extent) => { | ||
const basePosition = new Vector3( | ||
@@ -70,3 +107,3 @@ ...(coords[0] ?? scaleCoordinate(coords[1], extent)) | ||
lights.spotLights.value.push({ | ||
uniforms.spotLights.value.push({ | ||
color, | ||
@@ -73,0 +110,0 @@ baseDirection, |
@@ -0,1 +1,9 @@ | ||
// @ts-check | ||
/** | ||
* @param {number} value | ||
* @param {number} min | ||
* @param {number} max | ||
* @returns a clamped value kept between min and max. | ||
*/ | ||
export function clamp(value, min, max) { | ||
@@ -8,8 +16,14 @@ return Math.max(min, Math.min(max, value)); | ||
// Compute coefficients for a cubic polynomial | ||
// p(s) = c0 + c1 * s + c2 * s^2 + c3 * s^3 | ||
// such that | ||
// p(0) = x0, p(1) = x1 | ||
// and | ||
// p'(0) = t0, p'(1) = t1. | ||
/** | ||
* Compute coefficients for a cubic polynomial | ||
* p(s) = c0 + c1 * s + c2 * s^2 + c3 * s^3 | ||
* such that | ||
* p(0) = x0, p(1) = x1 | ||
* and | ||
* p'(0) = t0, p'(1) = t1. | ||
* @param {number} x0 | ||
* @param {number} x1 | ||
* @param {number} t0 | ||
* @param {number} t1 | ||
*/ | ||
function init(x0, x1, t0, t1) { | ||
@@ -23,2 +37,11 @@ c0 = x0; | ||
return { | ||
/** | ||
* @param {number} x0 | ||
* @param {number} x1 | ||
* @param {number} x2 | ||
* @param {number} x3 | ||
* @param {number} dt0 | ||
* @param {number} dt1 | ||
* @param {number} dt2 | ||
*/ | ||
initNonuniformCatmullRom: function (x0, x1, x2, x3, dt0, dt1, dt2) { | ||
@@ -35,2 +58,5 @@ // compute tangents when parameterized in [t1, t2] | ||
}, | ||
/** | ||
* @param {number} t | ||
*/ | ||
calc: function (t) { | ||
@@ -37,0 +63,0 @@ const t2 = t * t; |
@@ -0,1 +1,3 @@ | ||
// @ts-check | ||
import { | ||
@@ -15,6 +17,11 @@ BufferAttribute, | ||
// See the comments from primitives/index.js for more information about the | ||
// shape of a primitive function. | ||
// See https://mathics3.github.io/mathics-threejs-backend/primitives/arrow | ||
// for the high-level description of what is being rendered. | ||
/** @typedef {import('../coordinateUtils.js').Coordinate} Coordinate */ | ||
/** | ||
* See {@link PrimitiveFunction} for more information about the | ||
* shape of a primitive function. | ||
* See {@link https://mathics3.github.io/mathics-threejs-backend/primitives/arrow} | ||
* for the high-level description of what is being rendered. | ||
* @type {import('./index.js').PrimitiveFunction} | ||
*/ | ||
export default function ({ color = [0, 0, 0], coords, opacity = 1 }, uniforms, extent) { | ||
@@ -27,3 +34,6 @@ const material = getBasicMaterial(color, opacity); | ||
const startCoordinate = new Vector3( | ||
...(coords[coords.length - 2][0] ?? scaleCoordinate(coords[coords.length - 2][1], extent)) | ||
...(coords[coords.length - 2][0] ?? scaleCoordinate( | ||
/** @type {Coordinate} */(coords[coords.length - 2][1]), | ||
extent | ||
)) | ||
); | ||
@@ -33,3 +43,6 @@ | ||
const endCoordinate = new Vector3( | ||
...(coords[coords.length - 1][0] ?? scaleCoordinate(coords[coords.length - 1][1], extent)) | ||
...(coords[coords.length - 1][0] ?? scaleCoordinate( | ||
/** @type {Coordinate} */(coords[coords.length - 1][1]), | ||
extent | ||
)) | ||
); | ||
@@ -90,2 +103,3 @@ | ||
]), | ||
// @ts-expect-error: bad three.js typing | ||
material | ||
@@ -104,2 +118,3 @@ )); | ||
), | ||
// @ts-expect-error: bad three.js typing | ||
material | ||
@@ -106,0 +121,0 @@ ) |
@@ -0,1 +1,3 @@ | ||
// @ts-check | ||
import { | ||
@@ -14,6 +16,9 @@ BufferAttribute, | ||
// See the comments from primitives/index.js for more information about the | ||
// shape of a primitive function. | ||
// See https://mathics3.github.io/mathics-threejs-backend/primitives/cone | ||
// for the high-level description of what is being rendered. | ||
/** | ||
* See {@link PrimitiveFunction} for more information about the | ||
* shape of a primitive function. | ||
* See {@link https://mathics3.github.io/mathics-threejs-backend/primitives/cone} | ||
* for the high-level description of what is being rendered. | ||
* @type {import('./index.js').PrimitiveFunction} | ||
*/ | ||
export default function ({ color = [1, 1, 1], coords, edgeForm = {}, opacity = 1, radius = 1 }, uniforms, extent) { | ||
@@ -184,2 +189,3 @@ const [coneBases, coneTips] = get2PopulatedCoordinateBuffers(coords, extent); | ||
coneGeometry, | ||
// @ts-expect-error: bad three.js typing | ||
get2CoordinatesMaterial(color, opacity, uniforms) | ||
@@ -255,2 +261,3 @@ ); | ||
edgesGeometry, | ||
// @ts-expect-error: bad three.js typing | ||
new RawShaderMaterial({ | ||
@@ -257,0 +264,0 @@ vertexShader: `#version 300 es |
@@ -0,1 +1,3 @@ | ||
// @ts-check | ||
import { | ||
@@ -11,6 +13,9 @@ BufferAttribute, | ||
// See the comments from primitives/index.js for more information about the | ||
// shape of a primitive function. | ||
// See https://mathics3.github.io/mathics-threejs-backend/primitives/cuboid | ||
// for the high-level description of what is being rendered. | ||
/** | ||
* See {@link PrimitiveFunction} for more information about the | ||
* shape of a primitive function. | ||
* See {@link https://mathics3.github.io/mathics-threejs-backend/primitives/cuboid} | ||
* for the high-level description of what is being rendered. | ||
* @type {import('./index.js').PrimitiveFunction} | ||
*/ | ||
export default function ({ color = [1, 1, 1], coords, edgeForm = {}, opacity = 1 }, uniforms, extent) { | ||
@@ -109,2 +114,3 @@ // The edges of the cuboids are drawn in the fragment shader; doing this is faster than putting the edges in a different object. | ||
cuboidGeometry, | ||
// @ts-expect-error: bad three.js typing | ||
new RawShaderMaterial({ | ||
@@ -111,0 +117,0 @@ transparent: opacity !== 1, |
@@ -0,1 +1,3 @@ | ||
// @ts-check | ||
import { | ||
@@ -14,6 +16,9 @@ BufferAttribute, | ||
// See the comments from primitives/index.js for more information about the | ||
// shape of a primitive function. | ||
// See https://mathics3.github.io/mathics-threejs-backend/primitives/cylinder | ||
// for the high-level description of what is being rendered. | ||
/** | ||
* See {@link PrimitiveFunction} for more information about the | ||
* shape of a primitive function. | ||
* See {@link https://mathics3.github.io/mathics-threejs-backend/primitives/cylinder} | ||
* for the high-level description of what is being rendered. | ||
* @type {import('./index.js').PrimitiveFunction} | ||
*/ | ||
export default function ({ color = [1, 1, 1], coords, edgeForm = {}, opacity = 1, radius = 1 }, uniforms, extent) { | ||
@@ -380,2 +385,3 @@ const [cylindersBegin, cylindersEnd] = get2PopulatedCoordinateBuffers(coords, extent); | ||
cylinderGeometry, | ||
// @ts-expect-error: bad three.js typing | ||
get2CoordinatesMaterial(color, opacity, uniforms) | ||
@@ -484,2 +490,3 @@ ); | ||
edgesGeometry, | ||
// @ts-expect-error: bad three.js typing | ||
new RawShaderMaterial({ | ||
@@ -486,0 +493,0 @@ vertexShader: `#version 300 es |
@@ -0,1 +1,3 @@ | ||
// @ts-check | ||
// This file exports implmentations using three.js of Mathematica and | ||
@@ -7,17 +9,57 @@ // Mathics Graphics3D primitives like "Sphere", or "Cuboid, etc. | ||
// Each primitive function takes 4 parameters and returns a three.js | ||
// object. | ||
/** | ||
* Each primitive function takes 4 parameters and returns a three.js object | ||
* | ||
* The 1st parameter is the primitive object (an element of | ||
* the elements array). | ||
* | ||
* The 2nd parameter is the uniforms buffer, @see {@link src/uniforms.js} | ||
* for more information. | ||
* | ||
* The 3rd parameter is the extent, it is used in scaleCoordinate, | ||
* but it can be used for calculating border size, radius, ... | ||
* | ||
* The 4th parameter is the container, its style (CSS) is used for e.g. | ||
* calculating the points size. | ||
* | ||
* @typedef { 'arrow' | ||
* | 'cone' | ||
* | 'cuboid' | ||
* | 'cylinder' | ||
* | 'line' | ||
* | 'point' | ||
* | 'polygon' | ||
* | 'sphere' | ||
* | 'tube' | ||
* | 'uniformPolyhedron' | ||
* } PrimitiveType | ||
* | ||
* @typedef {import('../coordinateUtils.js').Coordinate} Coordinate | ||
* | ||
* @typedef {{ | ||
* type: PrimitiveType, | ||
* color?: [number, number, number], | ||
* coords: Array<[Coordinate, null] | [null, Coordinate]>, | ||
* dashed?: boolean, | ||
* edgeForm?: { | ||
* color?: [number, number, number], | ||
* showEdges?: boolean | ||
* }, | ||
* edgeLength?: number, | ||
* gapSize?: number, | ||
* opacity?: number, | ||
* pointSize?: number, | ||
* radius?: number, | ||
* subType?: 'tetrahedron' | 'octahedron' | 'dodecahedron' | 'icosahedron', | ||
* vertexNormals?: [number, number, number][] | ||
* }} PrimitiveElement | ||
* | ||
* @typedef {( | ||
* primitive: PrimitiveElement, | ||
* uniforms: import('../uniforms.js').UniformsBuffer, | ||
* extent: import('../extent.js').Extent, | ||
* container: HTMLElement | ||
* ) => import('../../vendors/three.js').Object3D } PrimitiveFunction | ||
*/ | ||
// The 1st parameter is the primitive object (an element of | ||
// the elements array). | ||
// The 2nd parameter is the uniforms buffer, read the comments from | ||
// src/uniforms.js for more information. | ||
// The 3rd parameter is the extent, it is used in scaleCoordinate, | ||
// but it can be used for calculating border size, radius, ... | ||
// The 4th parameter is the canvasSize, it is used for e.g. calculating the | ||
// points size. | ||
// Note that Graphics3D includes a number of 1D and 2D kinds of | ||
@@ -24,0 +66,0 @@ // objects, like Point, Line, Arrow, or Polygon which are extended |
@@ -0,1 +1,3 @@ | ||
// @ts-check | ||
import { | ||
@@ -11,8 +13,12 @@ BufferAttribute, | ||
// See the comments from primitives/index.js for more information about the | ||
// shape of a primitive function. | ||
// See https://mathics3.github.io/mathics-threejs-backend/primitives/line | ||
// for the high-level description of what is being rendered. | ||
// Differently from WL's Line, our lines aren't affected by | ||
// lightning and therefore don't have VertexNormals. | ||
/** | ||
* See {@link PrimitiveFunction} for more information about the | ||
* shape of a primitive function. | ||
* See {@link https://mathics3.github.io/mathics-threejs-backend/primitives/line} | ||
* for the high-level description of what is being rendered. | ||
* Differently from WL's Line, our lines aren't affected by | ||
* lightning and therefore don't have VertexNormals. | ||
* | ||
* @type {import('./index.js').PrimitiveFunction} | ||
*/ | ||
export default function ({ color = [0, 0, 0], coords, dashed = false, gapSize = 10, opacity = 1 }, uniforms, extent, container) { | ||
@@ -27,2 +33,3 @@ return new Line( | ||
), | ||
// @ts-expect-error: bad three.js typing | ||
dashed | ||
@@ -29,0 +36,0 @@ ? new RawShaderMaterial({ |
@@ -0,1 +1,3 @@ | ||
// @ts-check | ||
import { | ||
@@ -10,9 +12,13 @@ BufferAttribute, | ||
// See the comments from primitives/index.js for more information about the | ||
// shape of a primitive function. | ||
// See https://mathics3.github.io/mathics-threejs-backend/primitives/point | ||
// for the high-level description of what is being rendered. | ||
// Differently from WL's Point, our points aren't affected by | ||
// lightning and therefore don't have VertexNormals. | ||
export default function ({ color = [0, 0, 0], coords, opacity = 1, pointSize }, uniforms, extent, container) { | ||
/** | ||
* See {@link PrimitiveFunction} for more information about the | ||
* shape of a primitive function. | ||
* See {@link https://mathics3.github.io/mathics-threejs-backend/primitives/point} | ||
* for the high-level description of what is being rendered. | ||
* Differently from WL's Point, our points aren't affected by | ||
* lightning and therefore don't have VertexNormals. | ||
* | ||
* @type {import('./index.js').PrimitiveFunction} | ||
*/ | ||
export default function ({ color = [0, 0, 0], coords, opacity = 1, pointSize = 1 }, uniforms, extent, container) { | ||
return new Points( | ||
@@ -26,2 +32,3 @@ new BufferGeometry().setAttribute( | ||
), | ||
// @ts-expect-error: bad three.js typing | ||
new RawShaderMaterial({ | ||
@@ -28,0 +35,0 @@ transparent: true, |
@@ -0,1 +1,3 @@ | ||
// @ts-check | ||
import { | ||
@@ -7,2 +9,3 @@ BufferAttribute, | ||
Mesh, | ||
Quaternion, | ||
RawShaderMaterial, | ||
@@ -20,9 +23,15 @@ Vector3 | ||
// Get the unit normal vector from the 1st, 2nd and last coordinate | ||
// (these numbers were choosen because the vectors 1st->2nd and last->2nd | ||
// have different directions, what is necessary for some calculations) | ||
// Note: a "better" way to do this is compute an approximation plane | ||
// by taking linear least squares, but that would be way slower and | ||
// there would be only difference for very specific polygons. | ||
// (see https://en.wikipedia.org/wiki/Linear_least_squares) | ||
/** @typedef {import('../coordinateUtils.js').Coordinate} Coordinate */ | ||
/** | ||
* Get the unit normal vector from the 1st, 2nd and last coordinate | ||
* (these numbers were choosen because the vectors 1st->2nd and last->2nd | ||
* have different directions, what is necessary for some calculations) | ||
* Note: a "better" way to do this is compute an approximation plane | ||
* by taking linear least squares, but that would be way slower and | ||
* there would be only difference for very specific polygons. | ||
* (see https://en.wikipedia.org/wiki/Linear_least_squares) | ||
* @param {Array<[Coordinate, null] | [null, Coordinate]>} coordinates | ||
* @param {import('../extent.js').Extent} extent | ||
*/ | ||
function getNormalVector(coordinates, extent) { | ||
@@ -36,3 +45,6 @@ const vectorA = new Vector3( | ||
const vectorC = new Vector3( | ||
...coordinates[coordinates.length - 1][0] ?? scaleCoordinate(coordinates[coordinates.length - 1][1], extent) | ||
...coordinates[coordinates.length - 1][0] ?? scaleCoordinate( | ||
/** @type {Coordinate} */(coordinates[coordinates.length - 1][1]), | ||
extent | ||
) | ||
); | ||
@@ -45,7 +57,12 @@ | ||
// Test if the coordinates are coplanar by checking if the distance | ||
// of each coordinate to the plane is less than a threshold. | ||
// We don't need to do `coordinate -= distance to the plane` | ||
// because earcut returns the same indices for small differences. | ||
// (the indices of different objects are the same if they have the same shape) | ||
/** | ||
* Test if the coordinates are coplanar by checking if the distance | ||
* of each coordinate to the plane is less than a threshold. | ||
* We don't need to do `coordinate -= distance to the plane` | ||
* because earcut returns the same indices for small differences. | ||
* (the indices of different objects are the same if they have the same shape) | ||
* @param {Array<[Coordinate, null] | [null, Coordinate]>} coordinates | ||
* @param {import('../extent.js').Extent} extent | ||
* @returns {boolean} whether the coordinates are coplanar. | ||
*/ | ||
function isCoplanar(coordinates, extent) { | ||
@@ -66,3 +83,6 @@ // normal = ⟨A, B, C⟩ | ||
for (let i = 0; i < coordinates.length; i++) { | ||
const [x, y, z] = coordinates[i][0] ?? scaleCoordinate(coordinates[i][1], extent); | ||
const [x, y, z] = coordinates[i][0] ?? scaleCoordinate( | ||
/** @type {Coordinate} */(coordinates[i][1]), | ||
extent | ||
); | ||
@@ -81,7 +101,10 @@ // Given a point ⟨x, y, z⟩, the distance between the point | ||
// See the comments from primitives/index.js for more information about the | ||
// shape of a primitive function. | ||
// See https://mathics3.github.io/mathics-threejs-backend/primitives/polygon | ||
// for the high-level description of what is being rendered. | ||
export default function ({ color = [1, 1, 1], coords, edgeForm = {}, opacity = 1, vertexNormals = {} }, uniforms, extent) { | ||
/** | ||
* See {@link PrimitiveFunction} for more information about the | ||
* shape of a primitive function. | ||
* See {@link https://mathics3.github.io/mathics-threejs-backend/primitives/polygon} | ||
* for the high-level description of what is being rendered. | ||
* @type {import('./index.js').PrimitiveFunction} | ||
*/ | ||
export default function ({ color = [1, 1, 1], coords, edgeForm = {}, opacity = 1, vertexNormals = [] }, uniforms, extent) { | ||
let geometry; | ||
@@ -105,12 +128,20 @@ | ||
// The good news is that it has a 2d mode. | ||
// The really good news is that if we pass just pass the x and y | ||
// values from the coordinates earcut returns the correct indices. | ||
const quaternion = new Quaternion().setFromUnitVectors( | ||
getNormalVector(coords, extent), | ||
new Vector3(0, 0, 1) | ||
); | ||
const coordinates2d = new Float32Array(coords.length * 2); | ||
for (let i = 0; i < coords.length; i++) { | ||
coordinates2d[i * 2] = coords[i * 3]; | ||
coordinates2d[i * 2 + 1] = coords[i * 3 + 1]; | ||
} | ||
coords.forEach((coordinate, index) => { | ||
// apply the quaternion "zero" all z values, we can't draw a shape with non-zero z values | ||
const vector = new Vector3( | ||
...(coordinate[0] ?? scaleCoordinate(coordinate[1], extent)) | ||
).applyQuaternion(quaternion); | ||
coordinates2d[index * 2] = vector.x; | ||
coordinates2d[index * 2 + 1] = vector.y; | ||
}); | ||
geometry = new BufferGeometry() | ||
@@ -147,2 +178,4 @@ .setAttribute( | ||
// may have a different normal value). | ||
// @ts-expect-error: we already set the position attribute, so we are | ||
// sure it is there. | ||
const normals = new Float32Array(geometry.attributes.position.count * 3); | ||
@@ -162,2 +195,3 @@ | ||
geometry, | ||
// @ts-expect-error: bad three.js typing | ||
new RawShaderMaterial({ | ||
@@ -296,2 +330,3 @@ side: DoubleSide, | ||
geometry, | ||
// @ts-expect-error: bad three.js typing | ||
new RawShaderMaterial({ | ||
@@ -298,0 +333,0 @@ wireframe: true, |
@@ -0,1 +1,3 @@ | ||
// @ts-check | ||
import { | ||
@@ -10,6 +12,9 @@ InstancedBufferAttribute, | ||
// See the comments from primitives/index.js for more information about the | ||
// shape of a primitive function. | ||
// See https://mathics3.github.io/mathics-threejs-backend/primitives/sphere | ||
// for the high-level description of what is being rendered. | ||
/** | ||
* See {@link PrimitiveFunction} for more information about the | ||
* shape of a primitive function. | ||
* See {@link https://mathics3.github.io/mathics-threejs-backend/primitives/sphere} | ||
* for the high-level description of what is being rendered. | ||
* @type {import('./index.js').PrimitiveFunction} | ||
*/ | ||
export default function ({ color = [1, 1, 1], coords, opacity = 1, radius = 1 }, uniforms, extent) { | ||
@@ -26,2 +31,3 @@ const sphereGeometry = getSphereGeometry(radius, true) | ||
// @ts-expect-error: InstancedBufferGeometry have that attribute | ||
sphereGeometry.instanceCount = coords.length; | ||
@@ -31,2 +37,3 @@ | ||
sphereGeometry, | ||
// @ts-expect-error: bad three.js typing | ||
new RawShaderMaterial({ | ||
@@ -33,0 +40,0 @@ transparent: opacity !== 1, |
@@ -0,1 +1,3 @@ | ||
// @ts-check | ||
import { | ||
@@ -16,6 +18,9 @@ Matrix4, | ||
// See the comments from primitives/index.js for more information about the | ||
// shape of a primitive function. | ||
// See https://mathics3.github.io/mathics-threejs-backend/primitives/tube | ||
// for the high-level description of what is being rendered. | ||
/** | ||
* See {@link PrimitiveFunction} for more information about the | ||
* shape of a primitive function. | ||
* See {@link https://mathics3.github.io/mathics-threejs-backend/primitives/tube} | ||
* for the high-level description of what is being rendered. | ||
* @type {import('./index.js').PrimitiveFunction} | ||
*/ | ||
export default function ({ color = [1, 1, 1], coords, opacity = 1, radius = 1 }, uniforms, extent) { | ||
@@ -54,3 +59,10 @@ // We use getCentripetalCurve to convert an list of coordinates | ||
curve.getPoint(1), | ||
new Vector3(...(coords[coords.length - 2][0] ?? scaleCoordinate(coords[coords.length - 2][1], extent))), | ||
new Vector3( | ||
...(coords[coords.length - 2][0] | ||
?? scaleCoordinate( | ||
/** @type {import('../bufferUtils.js').Coordinate} */(coords[coords.length - 2][1]), | ||
extent | ||
) | ||
) | ||
), | ||
new Vector3(1, 0, 0) | ||
@@ -63,2 +75,3 @@ ) | ||
mergeBufferGeometries(geometries), | ||
// @ts-expect-error: bad three.js typing | ||
new RawShaderMaterial({ | ||
@@ -65,0 +78,0 @@ transparent: opacity !== 1, |
@@ -0,1 +1,3 @@ | ||
// @ts-check | ||
import { | ||
@@ -13,6 +15,9 @@ BufferAttribute, | ||
// See the comments from primitives/index.js for more information about the | ||
// shape of a primitive function. | ||
// See https://mathics3.github.io/mathics-threejs-backend/primitives/uniformPolyhedron | ||
// for the high-level description of what is being rendered. | ||
/** | ||
* See {@link PrimitiveFunction} for more information about the | ||
* shape of a primitive function. | ||
* See {@link https://mathics3.github.io/mathics-threejs-backend/primitives/uniformPolyhedron} | ||
* for the high-level description of what is being rendered. | ||
* @type {import('./index.js').PrimitiveFunction} | ||
*/ | ||
export default function ({ color = [1, 1, 1], coords, edgeForm = {}, edgeLength = 1, opacity = 1, subType }, uniforms, extent) { | ||
@@ -355,2 +360,3 @@ const polyhedronGeometry = new InstancedBufferGeometry(); | ||
polyhedronGeometry, | ||
// @ts-expect-error: bad three.js typing | ||
new RawShaderMaterial({ | ||
@@ -357,0 +363,0 @@ transparent: opacity !== 1, |
@@ -0,6 +1,14 @@ | ||
// @ts-check | ||
import { RawShaderMaterial } from '../vendors/three.js'; | ||
// returns a material with a shader that uses 2 attributes: | ||
// objectBegin and objectEnd. | ||
// In the case of the cones, objectEnd must be the cone tip. | ||
/** | ||
* Create a material with a shader that uses 2 attributes: | ||
* objectBegin and objectEnd. | ||
* In the case of the cones, objectEnd must be the cone tip. | ||
* @param {[number, number, number]} color | ||
* @param {number} opacity | ||
* @param {import('./uniforms.js').UniformsBuffer} uniforms | ||
* @returns the created material | ||
*/ | ||
export function get2CoordinatesMaterial(color, opacity, uniforms) { | ||
@@ -138,4 +146,8 @@ return new RawShaderMaterial({ | ||
// returns a material that ignores lighting and | ||
// has the same color for all its pixels. | ||
/** | ||
* @param {[number, number, number]} color | ||
* @param {number} opacity | ||
* @returns a material that ignores lighting so have all pixels | ||
* of the same color. | ||
*/ | ||
export function getBasicMaterial(color, opacity) { | ||
@@ -142,0 +154,0 @@ return new RawShaderMaterial({ |
@@ -1,29 +0,38 @@ | ||
// TODO: convert it to a real uniforms buffer | ||
// @ts-check | ||
/** | ||
* @typedef {{ | ||
* ambientLightColor: { value: [number, number, number] } | ||
* directionalLights: { value: { | ||
* color: [number, number, number], | ||
* direction: import('../vendors/three.js').Vector3 | ||
* }[] }, | ||
* pointLights: { value: { | ||
* color: [number, number, number], | ||
* basePosition: import('../vendors/three.js').Vector3, | ||
* position?: import('../vendors/three.js').Vector3 | ||
* }[] }, | ||
* spotLights: { value: { | ||
* color: [number, number, number], | ||
* baseDirection: import('../vendors/three.js').Vector3, | ||
* basePosition: import('../vendors/three.js').Vector3, | ||
* coneCos: number, | ||
* direction?: import('../vendors/three.js').Vector3, | ||
* position?: import('../vendors/three.js').Vector3 | ||
* }[] } | ||
* }} UniformsBuffer | ||
*/ | ||
/** | ||
* Factory method for uniforms buffer. | ||
* @todo convert this to a real uniforms buffer. | ||
* @returns {UniformsBuffer} | ||
*/ | ||
export function getUniformsBuffer() { | ||
return { | ||
ambientLightColor: { value: [0, 0, 0] }, | ||
directionalLights: { | ||
value: [], | ||
properties: { | ||
color: {}, // vec3 | ||
direction: {} // vec3 | ||
} | ||
}, | ||
pointLights: { | ||
value: [], | ||
properties: { | ||
color: {}, // vec3 | ||
position: {} // vec3 | ||
} | ||
}, | ||
spotLights: { | ||
value: [], | ||
properties: { | ||
color: {}, // vec3 | ||
coneCos: {}, // float | ||
direction: {}, // vec3 | ||
position: {} // vec3 | ||
} | ||
} | ||
directionalLights: { value: [] }, | ||
pointLights: { value: [] }, | ||
spotLights: { value: [] } | ||
}; | ||
} |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Mixed license
License(Experimental) Package contains multiple licenses.
Found 1 instance in 1 package
Native code
Supply chain riskContains native code (e.g., compiled binaries or shared libraries). Including native code can obscure malicious behavior.
Found 1 instance in 1 package
Mixed license
License(Experimental) Package contains multiple licenses.
Found 1 instance in 1 package
37351
3
2429106
9
163