/* eslint-disable no-useless-constructor */
/* eslint-disable no-unused-vars */
import { context } from '../context.js'
import { GVCartesian, GVCoordinates } from './coordinates.js'
import { aleaName } from '../outilsMathjs.js'
import { dot, round, cross } from 'mathjs'
import { latexParCoordonnees, latexParPoint, tracePoint, point, labelPoint } from '../2d.js'
import { circularPermutation, getDimensions } from './outils.js'
/**
* @class
* @classdesc Graphic object like Point, Line, Segment etc.
*/
export class GVGraphicObject {
visible /** boolean */
_name /** string */
color /** string */ = 'black'
constructor () {
this.visible = false
this.name = ''
}
aleaName (...name /** (string | GVGraphicObject)[] */) {
this.name = aleaName(name.map(x => {
if (x instanceof GVGraphicObject) {
return x.name
} else {
return x
}
}), name.length).join('')
}
set name (newname) {
this._name = newname
}
get name () { return this._name }
getGGB () {
return this.name
}
/**
* Move this right to figures
* @param figures
*/
moveRight (...figures/** GVGraphicObject[] */) {
const [xmin1, ymin1, xmax1, ymax1] = getDimensions(this)
const [xmin2, ymin2, xmax2, ymax2] = getDimensions(...figures)
const P1 = new GVPoint(xmin1, ymin1)
const P2 = new GVPoint(xmax2, ymax2)
const t = new GVVector(P1, P2)
this.move(t.add(new GVVector(4, 0)).sub(new GVVector(0, (ymax2 - ymin2 + ymax1 - ymin1) / 2)))
}
move (V /** GVVctor */) {
if (this instanceof GVPoint) {
this.x = this.add(V).x
this.y = this.add(V).y
} else if (this instanceof GVPolygon) {
for (const P of this.vertices) {
P.x = P.add(V).x
P.y = P.add(V).y
}
}
}
}
/**
* @class
* @classdesc Caracteristics of a point in an euclidean plan
*/
export class GVPoint extends GVGraphicObject {
coordinates /** GVCoordinates */
polarCoordinates /** GVPolar */
cartesianCoordinates /** GVCartesian */
type /** string */
x /** number */
y /** number */
r /** number */
theta /** number */
ggb /** string */
dot /** any */
labelPoints /** [GVPoint, GVPoint, GVPoint] */
label /** boolean */ = false
M2D /** any */
constructor (arg1/** GVCoordinates | number */, arg2 /** number */ = 0) {
super()
if (arg1 instanceof GVCoordinates) {
this.coordinates = arg1
} else {
this.coordinates = new GVCartesian(arg1, arg2)
}
this.polarCoordinates = this.getPolarCoordinates()
this.cartesianCoordinates = this.getCartesianCoordinates()
this.name = ''
this.type = 'Point'
this.x = this.cartesianCoordinates.x
this.y = this.cartesianCoordinates.y
this.r = this.polarCoordinates.r
this.theta = this.polarCoordinates.theta
this.ggb = `${this.name} = (${this.x},${this.y})`
this.M2D = point(this.x, this.y, '', 'above right')
}
getPolarCoordinates ()/** GVCartesian */ {
return this.coordinates.getPolarCoordinates()
}
getCartesianCoordinates () {
return this.coordinates.getCartesianCoordinates()
}
xSVG = function (coeff) {
return round(this.x * coeff, 3)
}
ySVG = function (coeff) {
return -round(this.y * coeff, 3)
}
getRotate (O /** GVPoint */, angle /** number */) {
return new GVPoint(
new GVCartesian(
(this.x - O.x) * Math.cos(angle) - (this.y - O.y) * Math.sin(angle) + O.x,
(this.x - O.x) * Math.sin(angle) + (this.y - O.y) * Math.cos(angle) + O.y
))
}
add (X /** GVVctor | GVPoint */) /** GVPoint */ {
return new GVPoint(new GVCartesian(this.x + X.x, this.y + X.y))
}
sub (X /** GVVctor | GVPoint */) /** GVPoint */ {
return new GVPoint(new GVCartesian(this.x - X.x, this.y - X.y))
}
multiply (k /** number */) /** GVPoint */ {
return new GVPoint(new GVCartesian(this.x * k, this.y * k))
}
divide (k /** number */) /** GVPoint */ {
return new GVPoint(new GVCartesian(this.x / k, this.y / k))
}
getBarycentriqueCoords (A /** GVPoint */, B /** GVPoint */, C /** GVPoint */) /** number[] */ {
const a /** number */ = determinant(B.sub(this), C.sub(this))
const b /** number */ = determinant(C.sub(this), A.sub(this))
const c /** number */ = determinant(A.sub(this), B.sub(this))
return [a, b, c]
}
isInTriangle (A /** GVPoint */, B /** GVPoint */, C /** GVPoint */) /** boolean */ {
return Math.min(...this.getBarycentriqueCoords(A, B, C)) > 0 || Math.max(...this.getBarycentriqueCoords(A, B, C)) < 0
}
/**
* Get the symétric of P with this
* @param P
* @returns {GVPoint}
*/
getSymetric (P /** GVPoint */) /** GVPoint */ {
return barycentre([this, P], [2, -1])
}
getHomothetic (O /** GVPoint */, k /** number */) {
return new GVPoint(
new GVCartesian(
k * this.x + (1 - k) * O.x,
k * this.y + (1 - k) * O.y
))
}
getVector () {
return new GVVector(this.x, this.y)
}
getGGB () {
this.ggb = `${this.name} = (${this.x},${this.y})`
return `${this.name} = (${this.x},${this.y})`
}
showName (scaleppc /** number */ = 1) /** string */ {
let label /** any */
const splitname = this.name.split('_')
let nameFormat = splitname.length === 1 ? splitname[0] : `${splitname[0]}_{${splitname[1]}}`
if (this.labelPoints !== undefined) {
const P1 = this.labelPoints[0]
const P3 = this.labelPoints[2]
const S = this.labelPoints[1]
const v1 = P1.sub(S).getVector().getNormed()
const v3 = P3.sub(S).getVector().getNormed()
const corr = new GVVector(0, -0.3 * scaleppc)
let P /** GVPoint */
if (v1.colinear(v3)) { // Colinéaires
P = S.add(v1.getNormal().multiply(scaleppc * 0.4)).add(corr)
} else { // Non colinéaires
P = S.getSymetric(S.add(v1.add(v3).getNormed().multiply(scaleppc * 0.4))).add(corr)
}
if (context.isHtml) {
label = latexParCoordonnees(nameFormat, P.x, P.y)
} else {
nameFormat = `$${nameFormat}$`
label = labelPoint(point(P.x, P.y, nameFormat, 'above'))
}
//
this.labelPoints = [P1, S, P3]
} else {
if (context.isHtml) {
label = latexParCoordonnees(nameFormat, this.x, this.y + 0.2 * scaleppc)
} else {
nameFormat = `$${nameFormat}$`
label = labelPoint(point(this.x, this.y, nameFormat, 'above'))
}
}
this.label = true
return label
}
showDot () {
const splitname = this.name.split('_')
let nameFormat = splitname.length === 1 ? splitname[0] : `${splitname[0]}_{${splitname[1]}}`
if (context.isHtml) nameFormat = `$${nameFormat}$`
this.dot = tracePoint(point(this.x, this.y, nameFormat, 'above'))
return this
}
set name (newname) {
this._name = newname
this.ggb = `${this.name} = (${this.x},${this.y})`
}
get name () { return this._name }
}
export class GVVector {
x /** number */ = 0
y /** number */ = 0
norme /** number */
constructor (arg1 /** number | GVPoint */, arg2 /** number | GVPoint */) {
if (typeof arg1 === 'number' && typeof arg2 === 'number') {
this.x = arg1
this.y = arg2
} else if (arg1 instanceof GVPoint && arg2 instanceof GVPoint) {
this.x = arg2.x - arg1.x
this.y = arg2.y - arg1.y
}
this.norme = Math.sqrt(this.x ** 2 + this.y ** 2)
}
getNormed () {
const xy = Math.sqrt(this.x ** 2 + this.y ** 2)
return new GVVector(this.x / xy, this.y / xy)
}
getNormal () /** GVVctor */ {
return new GVVector(-this.y, this.x)
}
add (X /** GVVctor | GVPoint */) /** GVVctor */ {
return new GVVector(this.x + X.x, this.y + X.y)
}
sub (X /** GVVctor | GVPoint */) /** GVVctor */ {
return new GVVector(this.x - X.x, this.y - X.y)
}
multiply (k /** number */) /** GVVctor */ {
return new GVVector(this.x * k, this.y * k)
}
neg () /** GVVctor */ {
return new GVVector(-this.x, -this.y)
}
dot (X /** GVVctor | GVPoint */) /** number */ {
return dot([this.x, this.y], [X.x, X.y])
}
cross (X /** GVVctor | GVPoint */)/** MathArray | Matrix */ {
const Cross = cross([this.x, this.y, 0], [X.x, X.y, 0])
return Cross
}
colinear (V /** GVVctor */) /** boolean */ {
return parseFloat(cross([this.x, this.y, 0], [V.x, V.y, 0])[2].toFixed(15)) === 0
}
}
/**
* @class
* @classdesc Caracteristics of a line in an euclidean plan (ax+by=c)
*/
export class GVLine extends GVGraphicObject {
direction /** GVVctor */
A /** GVPoint */
B /** GVPoint */
type /** string */
a /** number */ = 0
b /** number */ = 0
c /** number */ = 0
ggb /** string */
// Une droite sera définie par deux points distincts ou un point et une direction
// Il faudrait deux constructeurs ?
constructor (A /** GVPoint */, B /** GVPoint | GVVector */) {
super()
this.direction = B instanceof GVVector ? B : new GVVector(B.x - A.x, B.y - A.y)
this.A = A
this.B = B instanceof GVPoint ? B : new GVPoint(new GVCartesian(A.x + B.x, A.y + B.y))
this.getEquation()
this.type = 'Line'
this.ggb = `${this.name}: ${this.a}*x+${this.b}*y=${this.c})`
}
getYPoint (x /** number */) {
return this.b === 0 ? undefined : (this.c - this.a * x) / this.b
}
getXPoint (y /** number */) {
return this.a === 0 ? undefined : (this.c - this.b * y) / this.a
}
getEquation () {
const directionUnit = this.direction.getNormed()
this.a = -directionUnit.y
this.b = directionUnit.x
this.c = this.a * this.A.x + this.b * this.A.y
}
getIntersect (L/** GVLine */) /** GVPoint */ {
const delta = L.a * this.b - this.a * L.b
if (delta.toFixed(15) !== '0') {
const deltax = -(L.b * this.c - this.b * L.c)
const deltay = L.a * this.c - this.a * L.c
const point = new GVPoint(new GVCartesian(deltax / delta, deltay / delta))
return point
}
}
getPerpendicularLine (P /** GVPoint */) {
return new GVLine(P, this.direction.getNormal())
}
/**
* Get the symétric of P with this
* @param P
* @returns {GVPoint}
*/
getSymetric (P /** GVPoint */) /** GVPoint */ {
return barycentre([this.getIntersect(this.getPerpendicularLine(P)), P], [2, -1])
}
set name (newname) {
this._name = newname
this.ggb = `${this.name}: ${this.a}*x+${this.b}*y=${this.c})`
}
get name () { return this._name }
}
export function determinant (X /** GVVctor | GVPoint */, Y /** GVVctor | GVPoint */) /** number */ {
return X.x * Y.y - X.y * Y.x
}
export function barycentre (P /** GVPoint[] */, a /** number[] */) /** GVPoint */ {
const pointsPonderes = P.map((x, i) => x.multiply(a[i]))
return pointsPonderes.reduce((accumulator, curr) => accumulator.add(curr)).divide(a.reduce((accumulator, curr) => accumulator + curr))
}
/**
* @class
* @classdesc Caracteristics of a segment in an euclidean plan
*/
export class GVSegment extends GVLine {
label /** boolean */
text /** string */ = ''
textColor /** string */ = 'black'
direct /** boolean */ = true
constructor (A /** GVPoint */, B /** GVPoint */) {
super(A, B)
this.type = 'Segment'
this.A = A
this.B = B
this.aleaName(this.A, this.B)
this.getEquation()
}
showLabel (scaleppc /** number */ = 1) {
let label /** any */
const P = new GVPoint((this.A.x + this.B.x) / 2, (this.A.y + this.B.y) / 2)
if (context.isHtml) {
label = latexParPoint(this.name, point(P.x, P.y, '', 'center'), 'black', 50, 8, '')
// LatexParCoordonnees(texte, x, y, color, largeur, hauteur, colorBackground, tailleCaracteres)
} else {
label = latexParPoint(this.name, point(P.x, P.y, '', 'center'), 'black', 20, 8, '')
// label = labelPoint(point(P.x, P.y, `$${this.name}$`, 'center'))
}
this.label = true
return label
}
}
/**
* @class
* @classdesc Caracteristics of a circle in an euclidean plan
*/
export class GVCircle extends GVGraphicObject {
A /** GVPoint */ // center
B /** GVPoint | number */
type /** string */
a /** number */ = 0
b /** number */ = 0
r /** number */ = 0
constructor (A /** GVPoint */, B /** GVPoint | number */) {
super()
this.type = 'Circle'
this.A = A
this.B = B instanceof GVPoint ? B : A
this.r = B instanceof GVPoint ? Math.sqrt((A.x - B.x) ** 2 + (A.y - B.y) ** 2) : B
}
getPoint (theta /** number */) /** GVPoint */ {
return new GVPoint(
new GVCartesian(
this.A.x + this.r * Math.cos(theta),
this.A.y + this.r * Math.sin(theta)
)
)
}
}
/**
* @class
* @classdesc Caracteristics of an angle
*/
export class GVAngle extends GVGraphicObject {
A /** GVPoint */
B /** GVPoint */
C /** GVPoint */
angle /** number */
type /** string */
direct /** boolean */
vBA /** GVVctor */
vBC /** GVVctor */
right /** boolean */ = false
fillColor /** string */ = 'none'
fillOpacity /** number */ = 0.2
rayon /** boolean */ = true
constructor (A /** GVPoint */, B /** GVPoint */, C /** GVPoint */) {
super()
this.type = 'Angle'
const vA = new GVVector(A.x, A.y)
const vB = new GVVector(B.x, B.y)
const vC = new GVVector(C.x, C.y)
const vBA = vA.sub(vB).getNormed()
const vBC = vC.sub(vB).getNormed()
this.vBA = vBA
this.vBC = vBC
const cos = vBA.x * vBC.x + vBA.y * vBC.y
this.angle = Math.acos(cos)
this.A = B.add(vBA)
this.B = B
this.C = B.add(vBC)
this.direct = cross([vBA.x, vBA.y, 0], [vBC.x, vBC.y, 0])[2] > 0
}
scale (scale /** number */) {
const vBA = this.vBA.multiply(scale)
const vBC = this.vBC.multiply(scale)
this.A = this.B.add(vBA)
this.C = this.B.add(vBC)
}
}
/**
* @class
* @classdesc Caracteristics of an angle
*/
export class GVPolygon extends GVGraphicObject {
showLabels /** boolean */ = true
constructor (...args /** GVPoint[] */) {
super()
this.vertices /** GVPoint[] */= args
this.name = circularPermutation(args.map(x => x.name)).join('')
}
getDimensions () {
const listXPoint = this.vertices.map(M => M.x)
const listYPoint = this.vertices.map(M => M.y)
const xmin = Math.min(...listXPoint)
const xmax = Math.max(...listXPoint)
const ymin = Math.min(...listYPoint)
const ymax = Math.max(...listYPoint)
return [xmin, ymin, xmax, ymax]
}
}
/**
* @class
* @classdesc Caracteristics of a triangle
*/
export class GVRectangle extends GVPolygon {
longueur /** number */
largeur /** number */
ratio /** number */
constructor (...args /** GVPoint[] */) {
super(...args)
const [A, B, C, D] = args
const dimensions = [Math.sqrt((A.x - B.x) ** 2 + (A.y - B.y) ** 2), Math.sqrt((C.x - B.x) ** 2 + (C.y - B.y) ** 2)].sort()
this.largeur = dimensions[0]
this.longueur = dimensions[1]
this.ratio = this.longueur / this.largeur
}
}
/**
* @class
* @classdesc Caracteristics of a triangle
*/
export class GVTriangle extends GVPolygon {
constructor (...args /** GVPoint[] */) {
super(...args)
}
}