/*
Copyright 2008-2026
Matthias Ehmann,
Carsten Miller,
Andreas Walter,
Alfred Wassermann
This file is part of JSXGraph.
JSXGraph is free software dual licensed under the GNU LGPL or MIT License.
You can redistribute it and/or modify it under the terms of the
* GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version
OR
* MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT
JSXGraph is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License and
the MIT License along with JSXGraph. If not, see <https://www.gnu.org/licenses/>
and <https://opensource.org/licenses/MIT/>.
*/
/*global JXG:true, define: true*/
/**
* Create axes and rear and front walls of the
* view3d bounding box bbox3D.
*/
import JXG from "../jxg.js";
import Type from "../utils/type.js";
/**
* @class A container element that creates the axes and rear and front planes of a 3D view.
*
* Element `axes3d` is used to create
*
* - 3D coordinate axes (either `axesPosition:'border'` or `axesPosition:'center'`)
* - A point3d `O` (origin) if `axesPosition:'center'`
* - Rear and front planes in all three directions of the view3d element.
* - Coordinate axes on the rear and front planes
*
* @pseudo
* @name Axes3D
* @elementclass 3D
* @constructor
* @type Object
* @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
*
*/
/**
* @jsxgraphsignature Axes3D
*
*/
JXG.createAxes3D = function (board, parents, attributes) {
var view = parents[0],
directions = ["x", "y", "z"],
suffixAxis = "Axis",
sides = ["Rear", "Front"],
rear = [0, 0, 0], // x, y, z
front = [0, 0, 0], // x, y, z
i, j, k, i1, i2, attr, pos,
dir, dir1, len,
from, to, vec1, vec2,
range1, range2,
na, na_parent,
ticks_attr,
axes = {};
if (Type.exists(view.bbox3D)) {
for (i = 0; i < directions.length; i++) {
rear[i] = view.bbox3D[i][0];
front[i] = view.bbox3D[i][1];
}
} else {
for (i = 0; i < directions.length; i++) {
rear[i] = parents[1][i];
front[i] = parents[2][i];
}
}
// Main 3D axes
attr = Type.copyAttributes(attributes, board.options, 'axes3d');
// Position of the main axes can not be changed during run time
pos = attr.axesposition;
for (i = 0; i < directions.length; i++) {
// Run through ['x', 'y', 'z']
dir = directions[i];
na = dir + suffixAxis;
if (pos === 'center') {
// Axes centered
from = [0, 0, 0];
to = [0, 0, 0];
to[i] = front[i];
axes[na] = view.create('axis3d', [from, to], attr[na.toLowerCase()]);
axes[na].view = view;
} else if (pos === 'border') {
// Axes bordered
na += 'Border';
from = rear.slice();
to = front.slice();
if (dir === 'z') {
from[1] = front[1];
to[0] = rear[0];
} else if (dir === 'x') {
from = [rear[0], front[1], rear[2]];
to = [front[0], front[1], rear[2]];
} else {
from = [front[0], rear[1], rear[2]];
to = [front[0], front[1], rear[2]];
}
to[i] = front[i];
// attr[na.toLowerCase()].lastArrow = false;
axes[na] = view.create('axis3d', [from, to], attr[na.toLowerCase()]);
axes[na].view = view;
ticks_attr = attr[na.toLowerCase()].ticks3d;
ticks_attr.element3d = true; // Needed to avoid update during change of view
len = front[i] - rear[i];
if (dir === 'x') {
axes[na + "Ticks"] = view.create("ticks3d", [from, [1, 0, 0], len, [0, 1, 0]], ticks_attr);
} else if (dir === 'y') {
axes[na + "Ticks"] = view.create("ticks3d", [from, [0, 1, 0], len, [1, 0, 0]], ticks_attr);
} else {
axes[na + "Ticks"] = view.create("ticks3d", [from, [0, 0, 1], len, [0, 1, 0]], ticks_attr);
}
axes[na + "Ticks"].view = view;
}
}
if (pos === 'center') {
// Origin (2D point)
axes.O = view.create(
"intersection",
[axes[directions[0] + suffixAxis], axes[directions[1] + suffixAxis]],
{
name: "",
visible: false,
withLabel: false
}
);
axes.O.view = view;
} else {
axes.O = null;
}
// Front and rear planes
for (i = 0; i < directions.length; i++) {
// Run through ['x', 'y', 'z']
i1 = (i + 1) % 3;
i2 = (i + 2) % 3;
dir = directions[i];
for (j = 0; j < sides.length; j++) {
// Run through ['Rear', 'Front']
// attr = Type.copyAttributes(attributes, board.options, 'axes3d');
na = dir + "Plane" + sides[j];
from = [0, 0, 0];
from[i] = j === 0 ? rear[i] : front[i];
vec1 = [0, 0, 0];
vec2 = [0, 0, 0];
vec1[i1] = 1;
vec2[i2] = 1;
range1 = [rear[i1], front[i1]];
range2 = [rear[i2], front[i2]];
attr = Type.copyAttributes(attributes, board.options, "axes3d", na);
axes[na] = view.create('plane3d', [from, vec1, vec2, range1, range2], attr);
axes[na].elType = 'axisplane3d';
}
}
// Axes on front and rear planes
for (i = 0; i < directions.length; i++) {
// Run through ['x', 'y', 'z']
dir = directions[i];
for (j = 0; j < sides.length; j++) {
for (k = 1; k <= 2; k++) {
i1 = (i + k) % 3;
dir1 = directions[i1];
na = dir + "Plane" + sides[j] + dir1.toUpperCase() + 'Axis';
na_parent = dir + "Plane" + sides[j];
from = [0, 0, 0];
to = [0, 0, 0];
from[i] = to[i] = j === 0 ? rear[i] : front[i];
from[i1] = rear[i1];
to[i1] = front[i1];
attr = Type.copyAttributes(attributes, board.options, "axes3d", na);
axes[na] = view.create("axis3d", [from, to], attr);
axes[na].view = view;
axes[na_parent].addChild(axes[na]);
//if (Type.exists(axes[na_parent].element2D)) {
// TODO: Access of element2D is not nice
axes[na_parent].element2D.inherits.push(axes[na]);
//}
}
}
}
return axes;
};
JXG.registerElement("axes3d", JXG.createAxes3D);
/**
* @class A 3D axis element is a line together with optional ticks and labels.
* Simple element 3d axis as used with `axesPosition:center`. No ticks (yet).
*
* At the time being, the input arrays are NOT dynamic, i.e. can not be given as functions.
*
* @pseudo
* @name Axis3D
* @elementclass 3D
* @constructor
* @type Object
* @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
*/
/**
* @jsxgraphsignature Axis3D
* Two arrays of length 3 of type `[x, y, z]` for the start point and the end point of the axis.
* @param {Array} start
* @param {Array} end
*
*/
JXG.createAxis3D = function (board, parents, attributes) {
var view = parents[0],
attr = Type.copyAttributes(attributes, board.options, 'axis3d');
return view.create('line3d', parents.slice(1), attr);
};
JXG.registerElement('axis3d', JXG.createAxis3D);
// JXG.createAxis3DOld = function (board, parents, attributes) {
// var view = parents[0],
// attr,
// start = parents[1],
// end = parents[2],
// el_start,
// el_end,
// el;
//
// // Use 2D points to create axis
// attr = Type.copyAttributes(attributes.point1, board.options, "axis3d", 'point1');
// attr.element3d = true; // Needed to avoid update during change of view
// el_start = view.create(
// "point",
// [
// (function (xx, yy, zz) {
// return function () {
// return view.project3DTo2D(xx, yy, zz)[1];
// };
// })(start[0], start[1], start[2]),
// (function (xx, yy, zz) {
// return function () {
// return view.project3DTo2D(xx, yy, zz)[2];
// };
// })(start[0], start[1], start[2])
// ],
// attr
// );
//
// attr = Type.copyAttributes(attributes.point2, board.options, "axis3d", 'point2');
// attr.element3d = true; // Needed to avoid update during change of view
// el_end = view.create(
// "point",
// [
// (function (xx, yy, zz) {
// return function () {
// return view.project3DTo2D(xx, yy, zz)[1];
// };
// })(end[0], end[1], end[2]),
// (function (xx, yy, zz) {
// return function () {
// return view.project3DTo2D(xx, yy, zz)[2];
// };
// })(end[0], end[1], end[2])
// ],
// attr
// );
//
// attr = Type.copyAttributes(attributes, board.options, 'axis3d');
// attr.element3d = true; // Needed to avoid update during change of view
// el = view.create("arrow", [el_start, el_end], attr);
//
// return el;
// };
/**
* @class Display a rectangular mesh on a 3D plane element.
* Create a (rectangular) mesh - i.e. grid lines - on a plane3D element.
*
* At the time being, the mesh is not connected to the plane. The connecting element is simply the
* parameter point.
*
* @pseudo
* @name Mesh3D
* @elementclass 3D
* @constructor
* @type Object
* @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
*/
/**
* @jsxgraphsignature Mesh3D
* `range1` and `range2` (arrays of length 2) give the respective ranges.
* All parameters can be supplied as functions returning an appropriate data type.
*
* @param {Array} point Array of length 3 determining the starting point of the grid
* @param {Array} direction1 Array of length 3 for the directions of the grid
* @param {Array} direction2 Array of length 3 for the directions of the grid
* @param {Array} range1
* @param {Array} range2
*
*/
JXG.createMesh3D = function (board, parents, attributes) {
var view = parents[0],
attr, el;
attr = Type.copyAttributes(attributes, board.options, 'mesh3d');
attr.element3d = true; // Needed to avoid update during change of view
el = view.create("curve", [[], []], attr);
el.point = parents[1];
el.direction1 = parents[2];
el.direction2 = parents[3];
el.range1 = parents[4];
el.range2 = parents[5];
/**
* @ignore
*/
el.updateDataArray = function () {
var range1 = Type.evaluate(this.range1),
range2 = Type.evaluate(this.range2),
s1 = range1[0],
e1 = range1[1],
s2 = range2[0],
e2 = range2[1],
l1, l2, res, i,
v1 = [0, 0, 0],
v2 = [0, 0, 0],
step_u = this.evalVisProp('stepwidthu'),
step_v = this.evalVisProp('stepwidthv'),
q = [0, 0, 0];
this.dataX = [];
this.dataY = [];
if (Type.isFunction(this.point)) {
q = this.point();
} else {
if (Type.isPoint3D(this.point)) {
q = this.point.coords;
} else {
for (i = 0; i < this.point.length; i++) {
q[i] = Type.evaluate(this.point[i]);
}
}
}
if (Type.isFunction(this.direction1)) {
v1 = Type.evaluate(this.direction1);
} else {
for (i = 0; i < this.direction1.length; i++) {
v1[i] = Type.evaluate(this.direction1[i]);
}
}
if (Type.isFunction(this.direction2)) {
v2 = Type.evaluate(this.direction2);
} else {
for (i = 0; i < this.direction2.length; i++) {
v2[i] = Type.evaluate(this.direction2[i]);
}
}
if (q.length === 4) {
q = q.slice(1);
}
if (v1.length === 4) {
v1 = v1.slice(1);
}
if (v2.length === 4) {
v2 = v2.slice(1);
}
l1 = JXG.Math.norm(v1, 3);
l2 = JXG.Math.norm(v2, 3);
for (i = 0; i < 3; i++) {
v1[i] /= l1;
v2[i] /= l2;
}
// sol = Mat.Geometry.getPlaneBounds(v1, v2, q, s1, e1);
// if (sol !== null) {
// s1 = sol[0];
// e1 = sol[1];
// s2 = sol[2];
// e2 = sol[3];
// }
res = view.getMesh(
[
function (u, v) {
return q[0] + u * v1[0] + v * v2[0];
},
function (u, v) {
return q[1] + u * v1[1] + v * v2[1];
},
function (u, v) {
return q[2] + u * v1[2] + v * v2[2];
}
],
[Math.ceil(s1), Math.floor(e1), (Math.ceil(e1) - Math.floor(s1)) / step_u],
[Math.ceil(s2), Math.floor(e2), (Math.ceil(e2) - Math.floor(s2)) / step_v]
);
this.dataX = res[0];
this.dataY = res[1];
};
return el;
};
JXG.registerElement("mesh3d", JXG.createMesh3D);