diff --git a/packages/abstract-3d/package.json b/packages/abstract-3d/package.json new file mode 100644 index 00000000..6d78e8fa --- /dev/null +++ b/packages/abstract-3d/package.json @@ -0,0 +1,28 @@ +{ + "private": true, + "name": "@divid-local/abstract-3d", + "version": "0.1.0", + "description": "Abstract 3D", + "author": "Divid AB ", + "license": "UNLICENCED", + "type": "module", + "exports": { + ".": { + "ts": "./src/index.ts", + "import": "./lib/index.js" + } + }, + "dependencies": { + "ts-exhaustive-check": "^1.0.0", + "react": "^18.3.1", + "@react-three/fiber": "^8.16.6", + "@react-three/drei": "9.88.17", + "three": "^0.164.1", + "suspend-react": "^0.1.3" + }, + "devDependencies": { + "@types/three": "^0.164.1", + "@types/react": "^18.3.3", + "@types/react-reconciler": "^0.28.8" + } +} diff --git a/packages/abstract-3d/src/abstract-3d.ts b/packages/abstract-3d/src/abstract-3d.ts new file mode 100644 index 00000000..993b6d97 --- /dev/null +++ b/packages/abstract-3d/src/abstract-3d.ts @@ -0,0 +1,580 @@ +import * as THREE from "three"; + +export type Scene = { + readonly center: Vec3; + readonly size: Vec3; + readonly groups: ReadonlyArray; + readonly rotation?: Vec3; + readonly dimensions?: Dimensions; + readonly hotSpots?: ReadonlyArray; + readonly data?: Record; +}; + +export type Renderer = "react" | "ai_schematic" | "ai_detailed" | "dxf"; + +export type Dimensions = { + readonly dimensions: ReadonlyArray; + readonly material: Material; + readonly bounds: DimensionBounds; +}; + +export type Dimension = { + readonly views: ReadonlyArray; + readonly pos: Vec3; + readonly rot: Vec3; + readonly meshes: ReadonlyArray; +}; + +export type DimensionBounds = { + readonly front: Bounds2; + readonly back: Bounds2; + readonly left: Bounds2; + readonly right: Bounds2; + readonly top: Bounds2; + readonly bottom: Bounds2; + readonly threeD: Bounds3; +}; + +export type HotSpot = { + readonly id: string; + readonly mesh: Mesh; +}; + +export type Group = { + readonly pos: Vec3; + readonly rot: Vec3; + readonly groups: ReadonlyArray; + readonly meshes: ReadonlyArray; + readonly data: Record; + readonly animation?: Animation; +}; + +export type Mesh = { + readonly material: Material; + readonly geometry: Cylinder | Cone | Box | Line | Text | Polygon | Plane | Tube | Sphere | Shape; +}; + +export type Material = { + readonly type: MaterialType; + readonly normal: string; + readonly hover: string; + readonly selected: string; + readonly dxf: string; + readonly imageType: string; + readonly opacity: number; + readonly shininess: number; + readonly image: + | { readonly type: "HashImage"; readonly hash: string } + | { readonly type: "UrlImage"; readonly url: string } + | { readonly type: "NoImage" }; +}; + +export type MaterialType = "Phong" | "Lambert" | "Basic"; + +export type Cylinder = { + readonly type: "Cylinder"; + readonly pos: Vec3; + readonly rot: Vec3; + readonly length: number; + readonly radius: number; + readonly holes: ReadonlyArray; +}; + +export type Sphere = { + readonly type: "Sphere"; + readonly pos: Vec3; + readonly radius: number; +}; + +export type Cone = { + readonly type: "Cone"; + readonly pos: Vec3; + readonly rot: Vec3; + readonly length: number; + readonly radius: number; +}; + +export type Line = { + readonly type: "Line"; + readonly start: Vec3; + readonly end: Vec3; + readonly thickness: number; +}; + +export type Tube = { + readonly type: "Tube"; + readonly pos: Vec3; + readonly rot: Vec3; + readonly radius: number; + readonly curve: Curve; +}; + +export type Curve = CircleCurve | SplineCurve; + +export type CircleCurve = { + readonly type: "CircleCurve"; + readonly radius: number; + readonly angleStart: number; + readonly angleLength: number; +}; + +export type SplineCurve = { readonly type: "SplineCurve"; readonly points: ReadonlyArray }; + +export type Polygon = { + readonly type: "Polygon"; + readonly points: ReadonlyArray; + readonly pos: Vec3; + readonly rot: Vec3; +}; + +export type Shape = { + readonly type: "Shape"; + readonly points: ReadonlyArray; + readonly pos: Vec3; + readonly rot: Vec3; + readonly holes: ReadonlyArray; + readonly thickness: number; +}; + +export type Plane = { + readonly type: "Plane"; + readonly pos: Vec3; + readonly rot: Vec3; + readonly size: Vec2; + readonly holes: ReadonlyArray; +}; + +export type Text = { + readonly type: "Text"; + readonly pos: Vec3; + readonly rot: Vec3; + readonly text: string; + readonly fontSize: number; +}; + +export type Box = { + readonly type: "Box"; + readonly pos: Vec3; + readonly rot: Vec3; + readonly size: Vec3; + readonly holes: ReadonlyArray; +}; + +export type Hole = RoundHole | SquareHole; + +export type RoundHole = { + readonly type: "RoundHole"; + readonly pos: Vec2; + readonly radius: number; +}; + +export type SquareHole = { + readonly type: "SquareHole"; + readonly pos: Vec2; + readonly size: Vec2; +}; + +export type Animation = { + readonly transform: Transform; + readonly duration: number; +}; + +export type Transform = { + readonly rot: Vec3; + readonly trans: Vec3; +}; + +export type Bounds = { + readonly min: number; + readonly max: number; +}; + +export type Bounds2 = { + readonly min: Vec2; + readonly max: Vec2; +}; + +export type Bounds3 = { + readonly min: Vec3; + readonly max: Vec3; +}; + +export type Vec2 = { + readonly x: number; + readonly y: number; +}; + +export type Vec3 = { + readonly x: number; + readonly y: number; + readonly z: number; +}; + +export const views = ["front", "back", "top", "bottom", "left", "right"] as const; +export type View = (typeof views)[number]; + +export type PlaneMesh = { readonly geometry: Plane; readonly material: Material }; +export type BoxMesh = { readonly geometry: Box; readonly material: Material }; +export type CylinderMesh = { readonly geometry: Cylinder; readonly material: Material }; +export type ConeMesh = { readonly geometry: Cone; readonly material: Material }; +export type LineMesh = { readonly geometry: Line; readonly material: Material }; +export type TextMesh = { readonly geometry: Text; readonly material: Material }; +export type PolygonMesh = { readonly geometry: Polygon; readonly material: Material }; +export type TubeMesh = { readonly geometry: Tube; readonly material: Material }; +export type SphereMesh = { readonly geometry: Sphere; readonly material: Material }; +export type ShapeMesh = { readonly geometry: Shape; readonly material: Material }; + +export const vec3 = (x: number, y: number, z: number): Vec3 => ({ x, y, z }); +export const vec2 = (x: number, y: number): Vec2 => ({ x, y }); + +export const vec3Zero = vec3(0, 0, 0); +export const vec3PosX = vec3(1, 0, 0); +export const vec3NegX = vec3(-1, 0, 0); +export const vec3PosY = vec3(0, 1, 0); +export const vec3NegY = vec3(0, -1, 0); +export const vec3PosZ = vec3(0, 1, 0); +export const vec3NegZ = vec3(0, -1, 0); + +export const vec2Zero = vec2(0, 0); + +export const vec2Flip = (v: Vec2): Vec2 => vec2(-v.x, -v.y); +export const vec3Flip = (v: Vec3): Vec3 => vec3(-v.x, -v.y, -v.z); + +export const vec2Scale = (v: Vec2, s: number): Vec2 => vec2(v.x * s, v.y * s); +export const vec3Scale = (v: Vec3, s: number): Vec3 => vec3(v.x * s, v.y * s, v.z * s); + +export const vec2Add = (a: Vec2, b: Vec2): Vec2 => vec2(a.x + b.x, a.y + b.y); +export const vec3Add = (a: Vec3, b: Vec3): Vec3 => vec3(a.x + b.x, a.y + b.y, a.z + b.z); + +export const vec2Sub = (a: Vec2, b: Vec2): Vec2 => vec2(a.x - b.x, a.y - b.y); +export const vec3Sub = (a: Vec3, b: Vec3): Vec3 => vec3(a.x - b.x, a.y - b.y, a.z - b.z); + +export const vec2Dot = (a: Vec2, b: Vec2): number => a.x * b.x + a.y * b.y; +export const vec3Dot = (a: Vec3, b: Vec3): number => a.x * b.x + a.y * b.y + a.z * b.z; + +export const vec2Length = (v: Vec2): number => Math.sqrt(v.x * v.x + v.y * v.y); +export const vec3Length = (v: Vec3): number => Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z); + +export const vec2Normalize = (v: Vec2): Vec2 => vec2Scale(v, 1 / vec2Length(v)); +export const vec3Normalize = (v: Vec3): Vec3 => vec3Scale(v, 1 / vec3Length(v)); + +export const vec2Greater = (a: Vec2, b: Vec2): Vec2 => vec2(a.x > b.x ? a.x : b.x, a.y > b.y ? a.y : b.y); + +export const vec2Mult = (a: Vec2, b: Vec2): Vec2 => vec2(a.x * b.x, a.y * b.y); +export const vec3Mult = (a: Vec3, b: Vec3): Vec3 => vec3(a.x * b.x, a.y * b.y, a.z * b.z); + +export const vec2Dupl = (num: number): Vec2 => vec2(num, num); +export const vec3Dupl = (num: number): Vec3 => vec3(num, num, num); + +export const vec2Equals = (a: Vec2, b: Vec2): boolean => equals(a.x, b.x) && equals(a.y, b.y); +export const vec3Equals = (a: Vec3, b: Vec3): boolean => equals(a.x, b.x) && equals(a.y, b.y) && equals(a.z, b.z); + +export const vec3XMean = (...v: ReadonlyArray): number => v.reduce((a, c) => a + c.x, 0) / v.length; +export const vec3YMean = (...v: ReadonlyArray): number => v.reduce((a, c) => a + c.y, 0) / v.length; +export const vec3ZMean = (...v: ReadonlyArray): number => v.reduce((a, c) => a + c.z, 0) / v.length; + +export const vec3Greater = (a: Vec3, b: Vec3): Vec3 => + vec3(a.x > b.x ? a.x : b.x, a.y > b.y ? a.y : b.y, a.z > b.z ? a.z : b.z); + +export const equals = (num1: number, num2: number, equailty = Number.EPSILON): boolean => + Math.abs(num1 - num2) < equailty; +export const isZero = (num: number, equailty = Number.EPSILON): boolean => Math.abs(num) <= equailty; +export const geq = (num1: number, num2: number, equailty = Number.EPSILON): boolean => num1 >= num2 - equailty; +export const greater = (num1: number, num2: number, equailty = Number.EPSILON): boolean => num1 > num2 + equailty; +export const leq = (num1: number, num2: number, equailty = Number.EPSILON): boolean => num1 <= num2 + equailty; +export const less = (num1: number, num2: number, equailty = Number.EPSILON): boolean => num1 < num2 - equailty; + +// -- Bounds + +export const bounds = (min: number, max: number): Bounds => ({ min, max }); +export const bounds2 = (min: Vec2, max: Vec2): Bounds2 => ({ min, max }); +export const bounds3 = (min: Vec3, max: Vec3): Bounds3 => ({ min, max }); + +export const boundsZero: Bounds = bounds(0, 0); +export const bounds2Zero: Bounds2 = bounds2(vec2Zero, vec2Zero); +export const bounds3Zero: Bounds3 = bounds3(vec3Zero, vec3Zero); + +export const bounds3ToSize = (bounds: Bounds3): Vec3 => + vec3(bounds.max.x - bounds.min.x, bounds.max.y - bounds.min.y, bounds.max.z - bounds.min.z); + +export const bounds3Overlap = (a: Bounds3, b: Bounds3, equailty = Number.EPSILON): boolean => + !leq(a.max.x, b.min.x, equailty) && + !geq(a.min.x, b.max.x, equailty) && + !leq(a.max.y, b.min.y, equailty) && + !geq(a.min.y, b.max.y, equailty) && + !leq(a.max.z, b.min.z, equailty) && + !geq(a.min.z, b.max.z, equailty); + +export const boundsOverlap = (a: Bounds, b: Bounds): boolean => + (greater(a.max, b.min) && less(a.min, b.max)) || (greater(b.max, a.min) && less(b.min, a.max)); + +export const bounds2OverlapY = (a: Bounds2, b: Bounds2): boolean => + (greater(a.max.y, b.min.y) && less(a.min.y, b.max.y)) || (greater(b.max.y, a.min.y) && less(b.min.y, a.max.y)); + +export const boundsXOverlapX = (a: Bounds2, b: Bounds2): boolean => + (greater(a.max.x, b.min.x) && less(a.min.x, b.max.x)) || (greater(b.max.x, a.min.x) && less(b.min.x, a.max.x)); + +export const bounds3Center = (bounds: Bounds3): Vec3 => + vec3((bounds.min.x + bounds.max.x) / 2, (bounds.min.y + bounds.max.y) / 2, (bounds.min.z + bounds.max.z) / 2); + +export function bounds2FromPosAndSize(pos: Vec2, size: Vec2): Bounds2 { + const halfX = size.x * 0.5; + const halfY = size.y * 0.5; + return bounds2(vec2(pos.x - halfX, pos.y - halfY), vec2(pos.x + halfX, pos.y + halfY)); +} + +export function bounds3FromPosAndSize(pos: Vec3, size: Vec3): Bounds3 { + const halfX = size.x * 0.5; + const halfY = size.y * 0.5; + const halfZ = size.z * 0.5; + return bounds3(vec3(pos.x - halfX, pos.y - halfY, pos.z - halfZ), vec3(pos.x + halfX, pos.y + halfY, pos.z + halfZ)); +} + +export const bounds2Merge = (a: Bounds2, b: Bounds2): Bounds2 => { + return bounds2( + vec2(Math.min(a.min.x, b.min.x), Math.min(a.min.y, b.min.y)), + vec2(Math.max(a.max.x, b.max.x), Math.max(a.max.y, b.max.y)) + ); +}; + +export const bounds3Merge = (...bounds: ReadonlyArray): Bounds3 => { + if (bounds.length === 0) { + return bounds3Zero; + } + const min = vec3Dupl(Number.MAX_VALUE) as { x: number; y: number; z: number }; + const max = vec3Dupl(-Number.MAX_VALUE) as { x: number; y: number; z: number }; + bounds.forEach((b) => { + if (b.min.x < min.x) { + min.x = b.min.x; + } + if (b.min.y < min.y) { + min.y = b.min.y; + } + if (b.min.z < min.z) { + min.z = b.min.z; + } + if (b.max.x > max.x) { + max.x = b.max.x; + } + if (b.max.y > max.y) { + max.y = b.max.y; + } + if (b.max.z > max.z) { + max.z = b.max.z; + } + }); + return bounds3(min, max); +}; + +export function bounds3FromVec3Array(vec3Array: ReadonlyArray): Bounds3 { + const min = vec3Dupl(Number.MAX_VALUE) as { x: number; y: number; z: number }; + const max = vec3Dupl(-Number.MAX_VALUE) as { x: number; y: number; z: number }; + vec3Array.forEach((v) => { + if (v.x < min.x) { + min.x = v.x; + } + if (v.y < min.y) { + min.y = v.y; + } + if (v.z < min.z) { + min.z = v.z; + } + if (v.x > max.x) { + max.x = v.x; + } + if (v.y > max.y) { + max.y = v.y; + } + if (v.z > max.z) { + max.z = v.z; + } + }); + return bounds3(min, max); +} + +// -- Transformations + +const quaternion1 = new THREE.Quaternion(); +const quaternion2 = new THREE.Quaternion(); +const euler1 = new THREE.Euler(); +const euler2 = new THREE.Euler(); +const vector = new THREE.Vector3(); + +export function vec3RotCombine(outer: Vec3, inner: Vec3): Vec3 { + euler1.set(outer.x, outer.y, outer.z); + quaternion1.setFromEuler(euler1); + euler2.set(inner.x, inner.y, inner.z); + quaternion2.setFromEuler(euler2); + quaternion1.multiply(quaternion2); + euler1.setFromQuaternion(quaternion1); + return vec3(euler1.x, euler1.y, euler1.z); +} + +export function vec3Rot(point: Vec3, origin: Vec3, rotation: Vec3): Vec3 { + euler1.set(rotation.x, rotation.y, rotation.z); + quaternion1.setFromEuler(euler1); + vector.set(point.x - origin.x, point.y - origin.y, point.z - origin.z); + vector.applyQuaternion(quaternion1); + return vec3(vector.x + origin.x, vector.y + origin.y, vector.z + origin.z); +} + +export const vec3TransRot = (p: Vec3, pos: Vec3, rot: Vec3): Vec3 => vec3Rot(vec3Add(p, pos), pos, rot); + +export function geoRot(g: T, origin: Vec3, rot: Vec3): T { + return { ...g, pos: vec3Rot(g.pos, origin, rot), rot: vec3RotCombine(rot, g.rot) }; +} +export function geoTrans(g: T, translate: Vec3): T { + return { ...g, pos: vec3Add(g.pos, translate) }; +} +export function geoTransRot( + g: T, + origin: Vec3, + rot: Vec3 +): T { + return { ...g, pos: vec3Rot(vec3Add(g.pos, origin), origin, rot), rot: vec3RotCombine(rot, g.rot) }; +} + +export function sphereRotTrans(s: Sphere, origin: Vec3, rot: Vec3): Sphere { + return { ...s, pos: vec3Rot(vec3Add(s.pos, origin), origin, rot) }; +} + +export const lineRot = (l: Line, origin: Vec3, rot: Vec3): Line => ({ + ...l, + start: vec3Rot(l.start, origin, rot), + end: vec3Rot(l.end, origin, rot), +}); +export const lineTrans = (l: Line, translation: Vec3): Line => ({ + ...l, + start: vec3Add(l.start, translation), + end: vec3Add(l.end, translation), +}); +export const lineRotTrans = (l: Line, origin: Vec3, rot: Vec3): Line => ({ + ...l, + start: vec3TransRot(l.start, origin, rot), + end: vec3Rot(l.end, origin, rot), +}); + +export const polygonRot = (p: Polygon, origin: Vec3, rot: Vec3): Polygon => ({ + ...p, + points: p.points.map((p) => vec3Rot(p, origin, rot)), +}); +export const polygonTrans = (p: Polygon, translation: Vec3): Polygon => ({ + ...p, + points: p.points.map((p) => vec3Add(p, translation)), +}); +export const polygonRotTrans = (p: Polygon, origin: Vec3, rot: Vec3): Polygon => ({ + ...p, + points: p.points.map((p) => vec3TransRot(p, origin, rot)), +}); + +// -- Constructors + +export const group = ( + meshes: ReadonlyArray, + pos = vec3Zero, + rot = vec3Zero, + groups = Array(), + data = {}, + animation: Animation | undefined = undefined +): Group => ({ meshes, pos, rot, data, groups, animation: animation! }); + +export const boxMesh = (box: Box, material: Material): Mesh => ({ geometry: box, material }); +export const boxGeometry = ( + size: Vec3, + pos: Vec3 = vec3Zero, + rot = vec3Zero, + holes: ReadonlyArray = [] +): Box => ({ + type: "Box", + pos, + rot, + size, + holes, +}); +export const box = ( + size: Vec3, + material: Material, + pos: Vec3 = vec3Zero, + rot = vec3Zero, + holes: ReadonlyArray = [] +): BoxMesh => ({ + geometry: { type: "Box", pos, rot, size, holes }, + material, +}); + +export const roundHole = (pos: Vec2, radius: number): Hole => ({ type: "RoundHole", pos, radius }); +export const squareHole = (pos: Vec2, size: Vec2): Hole => ({ type: "SquareHole", pos, size }); + +export const plane = ( + pos: Vec3, + size: Vec2, + material: Material, + rot = vec3Zero, + holes: ReadonlyArray = [] +): PlaneMesh => ({ + geometry: { type: "Plane", pos, rot, size, holes }, + material, +}); + +export const cone = (pos: Vec3, radius: number, length: number, material: Material, rot = vec3Zero): ConeMesh => ({ + geometry: { type: "Cone", pos, radius, length, rot }, + material, +}); + +export const cylinder = ( + pos: Vec3, + radius: number, + length: number, + material: Material, + rot = vec3Zero, + holes: ReadonlyArray = [] +): CylinderMesh => ({ geometry: { type: "Cylinder", pos, radius, length, rot, holes }, material }); + +export const sphere = (radius: number, material: Material, pos = vec3Zero): SphereMesh => ({ + geometry: { type: "Sphere", pos, radius }, + material, +}); + +export const shape = ( + points: ReadonlyArray, + pos: Vec3, + material: Material, + thickness = 0, + rot = vec3Zero, + holes: ReadonlyArray = [] +): ShapeMesh => ({ geometry: { type: "Shape", points, pos, rot, thickness, holes }, material }); + +export const polygon = ( + points: ReadonlyArray, + material: Material, + pos = vec3Zero, + rot = vec3Zero +): PolygonMesh => ({ + geometry: { type: "Polygon", points, pos, rot }, + material, +}); + +export const lineMesh = (line: Line, material: Material): Mesh => ({ geometry: line, material }); +export const lineGeo = (start: Vec3, end: Vec3, thickness: number): Line => ({ type: "Line", start, end, thickness }); +export const line = (start: Vec3, end: Vec3, thickness: number, material: Material): LineMesh => ({ + geometry: { type: "Line", start, end, thickness }, + material, +}); + +export const text = (pos: Vec3, text: string, fontSize: number, material: Material, rot = vec3Zero): TextMesh => ({ + geometry: { type: "Text", pos, text, fontSize, rot }, + material, +}); + +export const tube = (curve: Curve, radius: number, material: Material, pos: Vec3 = vec3Zero, rot = vec3Zero): Mesh => ({ + geometry: { type: "Tube", curve, radius, pos, rot }, + material, +}); + +export const circleCurve = (radius: number, angleStart: number, angleLength: number): CircleCurve => ({ + type: "CircleCurve", + radius, + angleLength, + angleStart, +}); +export const splineCurve = (points: ReadonlyArray): SplineCurve => ({ type: "SplineCurve", points }); diff --git a/packages/abstract-3d/src/index.ts b/packages/abstract-3d/src/index.ts new file mode 100644 index 00000000..20dc5b49 --- /dev/null +++ b/packages/abstract-3d/src/index.ts @@ -0,0 +1,2 @@ +export * from "./renderers/index.js"; +export * from "./abstract-3d.js"; diff --git a/packages/abstract-3d/src/renderers/dxf/dxf-encoding.ts b/packages/abstract-3d/src/renderers/dxf/dxf-encoding.ts new file mode 100644 index 00000000..377f7950 --- /dev/null +++ b/packages/abstract-3d/src/renderers/dxf/dxf-encoding.ts @@ -0,0 +1,348 @@ +import { Vec3 } from "../../abstract-3d.js"; + +export const dxf3DFACE = (vec1: Vec3, vec2: Vec3, vec3: Vec3, vec4: Vec3, color: string): string => + ` 0 +3DFACE + 8 +A3D + 6 +CONTINUOUS + 62 +${color} + 10 +${vec1.x} + 20 +${vec1.y} + 30 +${vec1.z} + 11 +${vec2.x} + 21 +${vec2.y} + 31 +${vec2.z} + 12 +${vec3.x} + 22 +${vec3.y} + 32 +${vec3.z} + 13 +${vec4.x} + 23 +${vec4.y} + 33 +${vec4.z} +`; + +export const dxfPOLYLINE = (vertices: ReadonlyArray, color: string): string => + ` 0 +POLYLINE + 8 +A3D + 62 +${color} + 66 +1${vertices.map( + (v) => + ` + 0 +VERTEX + 8 +A3D + 10 +${v.x} + 20 +${v.y} + 30 +${v.z}` + )} + 0 +SEQEND +`; + +export const dxfText = (pos: Vec3, fontSize: number, text: string, color: string): string => + ` 0 +Text + 8 +A3D + 62 +${color} + 10 +${pos.x} + 20 +${pos.y} + 30 +${pos.z} + 11 +${pos.x} + 21 +${pos.y} + 31 +${pos.z} + 40 +${fontSize} + 1 +${text} +`; + +export const dxfHeader = (size: Vec3, center: Vec3): string => + ` 999 +Divid A3D + 0 +SECTION + 2 +HEADER + 9 +$ACADVER + 1 +AC1006 + 9 +$INSBASE + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$EXTMIN + 10 +${0} + 20 +${0} + 30 +${0} + 9 +$EXTMAX + 10 +${size.x} + 20 + ${size.y} + 30 + ${size.z} + 0 +ENDSEC + 0 +SECTION + 2 +TABLES + 0 +TABLE + 2 +APPID + 70 +2 + 0 +APPID + 2 +ACAD + 70 +1 + 0 +APPID + 2 +A3D + 70 +1 + 0 +ENDTAB + 0 +TABLE + 2 +LTYPE + 70 +1 + 0 +LTYPE + 2 +CONTINUOUS + 70 +0 + 3 +Solid line + 72 +65 + 73 +0 + 40 +0.0 + 0 +ENDTAB + 0 +TABLE + 2 +LAYER + 70 +3 + 0 +LAYER + 2 +0 + 70 +0 + 62 +7 + 6 +CONTINUOUS + 0 +LAYER + 2 +A3D + 70 +0 + 62 +230 + 6 +CONTINUOUS + 0 +LAYER + 2 +A3D + 70 +0 + 62 +7 + 6 +CONTINUOUS + 0 +ENDTAB + 0 +TABLE + 2 +STYLE + 70 +0 + 0 +ENDTAB + 0 +TABLE + 2 +VPORT + 70 +1 + 0 +VPORT + 2 +*ACTIVE + 70 +0 + 10 +0.0 + 20 +0.0 + 11 +1.0 + 21 +1.0 + 12 +${-size.x / 2} + 22 +${0} + 13 +0.0 + 23 +0.0 + 14 +1.0 + 24 +1.0 + 15 +0.0 + 25 +0.0 + 16 +0 + 26 +0 + 36 +1 + 17 +${center.x} + 27 +${center.y} + 37 +0 + 40 +${size.y} + 41 +${size.x / size.y} + 42 +50.0 + 43 +0.0 + 44 +0.0 + 50 +0.0 + 51 +0.0 + 71 +0 + 72 +0 + 73 +0 + 74 +0 + 75 +0 + 76 +0 + 77 +0 + 78 +0 + 0 +ENDTAB + 0 +ENDSEC + 0 +SECTION + 2 +BLOCKS + 0 +BLOCK + 8 +A3D + 2 +A3D_Group + 70 +64 + 10 +0.0 + 20 +0.0 + 30 +0.0 + 3 +A3D_Group +`; + +export const dxfFooter = ` 0 +ENDBLK + 0 +ENDSEC + 0 +SECTION + 2 +ENTITIES + 0 +INSERT + 8 +A3D + 6 +CONTINUOUS + 10 +0.0 + 20 +0.0 + 30 +0.0 + 2 +A3D_Group + 0 +ENDSEC + 0 +EOF +`; diff --git a/packages/abstract-3d/src/renderers/dxf/dxf-geometries/dxf-box.ts b/packages/abstract-3d/src/renderers/dxf/dxf-geometries/dxf-box.ts new file mode 100644 index 00000000..1d41e172 --- /dev/null +++ b/packages/abstract-3d/src/renderers/dxf/dxf-geometries/dxf-box.ts @@ -0,0 +1,27 @@ +import * as A3D from "../../../abstract-3d.js"; +import { dxf3DFACE } from "../dxf-encoding.js"; + +export function dxfBox(b: A3D.Box, m: A3D.Material, parentPos: A3D.Vec3, parentRot: A3D.Vec3): string { + const pos = A3D.vec3TransRot(b.pos, parentPos, parentRot); + const rot = A3D.vec3RotCombine(parentRot, b.rot); + const half = A3D.vec3Scale(b.size, 0.5); + const vec3tr3 = (x: number, y: number, z: number): A3D.Vec3 => A3D.vec3TransRot(A3D.vec3(x, y, z), pos, rot); + + const v1 = vec3tr3(-half.x, -half.y, half.z); + const v2 = vec3tr3(half.x, -half.y, half.z); + const v3 = vec3tr3(half.x, half.y, half.z); + const v4 = vec3tr3(-half.x, half.y, half.z); + const v5 = vec3tr3(-half.x, -half.y, -half.z); + const v6 = vec3tr3(half.x, -half.y, -half.z); + const v7 = vec3tr3(half.x, half.y, -half.z); + const v8 = vec3tr3(-half.x, half.y, -half.z); + + return ( + dxf3DFACE(v1, v2, v3, v4, m.dxf) + // front + dxf3DFACE(v5, v6, v7, v8, m.dxf) + // Back + dxf3DFACE(v5, v1, v4, v8, m.dxf) + // Left + dxf3DFACE(v6, v2, v3, v7, m.dxf) + // Right + dxf3DFACE(v8, v7, v3, v4, m.dxf) + // Top + dxf3DFACE(v5, v6, v2, v1, m.dxf) // Bottom + ); +} diff --git a/packages/abstract-3d/src/renderers/dxf/dxf-geometries/dxf-cone.ts b/packages/abstract-3d/src/renderers/dxf/dxf-geometries/dxf-cone.ts new file mode 100644 index 00000000..df599876 --- /dev/null +++ b/packages/abstract-3d/src/renderers/dxf/dxf-geometries/dxf-cone.ts @@ -0,0 +1,31 @@ +import * as A3D from "../../../abstract-3d.js"; +import { dxf3DFACE } from "../dxf-encoding.js"; + +export function dxfCone(c: A3D.Cone, m: A3D.Material, sides: number, parentPos: A3D.Vec3, parentRot: A3D.Vec3): string { + let dxfString = ""; + const pos = A3D.vec3TransRot(c.pos, parentPos, parentRot); + const rot = A3D.vec3RotCombine(parentRot, c.rot); + const vec3tr2 = (x: number, y: number, z: number): A3D.Vec3 => A3D.vec3TransRot(A3D.vec3(x, y, z), pos, rot); + + const angleStep = (2 * Math.PI) / sides; + let currentAngle = 0; + + const half = c.length / 2; + const botPos = vec3tr2(0, -half, 0); + const topPos = vec3tr2(0, half, 0); + + const botVec3Array = Array(); + for (let i = 0; i <= sides; i++) { + const currBot = vec3tr2(Math.sin(currentAngle) * c.radius, -half, Math.cos(currentAngle) * c.radius); + botVec3Array.push(currBot); + + if (i !== 0) { + const prevBot = botVec3Array[i - 1]!; + dxfString += + dxf3DFACE(botPos, prevBot, currBot, currBot, m.dxf) + dxf3DFACE(currBot, prevBot, topPos, topPos, m.dxf); + } + currentAngle += angleStep; + } + + return dxfString; +} diff --git a/packages/abstract-3d/src/renderers/dxf/dxf-geometries/dxf-cylinder.ts b/packages/abstract-3d/src/renderers/dxf/dxf-geometries/dxf-cylinder.ts new file mode 100644 index 00000000..277f61b5 --- /dev/null +++ b/packages/abstract-3d/src/renderers/dxf/dxf-geometries/dxf-cylinder.ts @@ -0,0 +1,45 @@ +import * as A3D from "../../../abstract-3d.js"; +import { dxf3DFACE } from "../dxf-encoding.js"; + +export function dxfCylinder( + c: A3D.Cylinder, + m: A3D.Material, + sides: number, + parentPos: A3D.Vec3, + parentRot: A3D.Vec3 +): string { + let dxfString = ""; + const pos = A3D.vec3TransRot(c.pos, parentPos, parentRot); + const rot = A3D.vec3RotCombine(parentRot, c.rot); + const vec3tr = (x: number, y: number, z: number): A3D.Vec3 => A3D.vec3TransRot(A3D.vec3(x, y, z), pos, rot); + + const angleStep = (2 * Math.PI) / sides; + let currentAngle = 0; + + const half = c.length / 2; + const topPos = vec3tr(0, half, 0); + const botPos = vec3tr(0, -half, 0); + + const botVec3Array = Array(); + const topVec3Array = Array(); + + for (let i = 0; i <= sides; i++) { + const x = Math.sin(currentAngle) * c.radius; + const z = Math.cos(currentAngle) * c.radius; + const currBot = vec3tr(x, -half, z); + const currTop = vec3tr(x, half, z); + botVec3Array.push(currBot); + topVec3Array.push(currTop); + if (i !== 0) { + const prevBot = botVec3Array[i - 1]!; + const prevTop = topVec3Array[i - 1]!; + dxfString += + dxf3DFACE(botPos, prevBot, currBot, currBot, m.dxf) + + dxf3DFACE(topPos, prevTop, currTop, currTop, m.dxf) + + dxf3DFACE(currBot, prevBot, prevTop, currTop, m.dxf); + } + currentAngle += angleStep; + } + + return dxfString; +} diff --git a/packages/abstract-3d/src/renderers/dxf/dxf-geometries/dxf-plane.ts b/packages/abstract-3d/src/renderers/dxf/dxf-geometries/dxf-plane.ts new file mode 100644 index 00000000..8846d060 --- /dev/null +++ b/packages/abstract-3d/src/renderers/dxf/dxf-geometries/dxf-plane.ts @@ -0,0 +1,16 @@ +import * as A3D from "../../../abstract-3d.js"; +import { dxf3DFACE } from "../dxf-encoding.js"; + +export function dxfPlane(p: A3D.Plane, m: A3D.Material, parentPos: A3D.Vec3, parentRot: A3D.Vec3): string { + const half = A3D.vec2Scale(p.size, 0.5); + const pos = A3D.vec3TransRot(p.pos, parentPos, parentRot); + const rot = A3D.vec3RotCombine(parentRot, p.rot); + const vec3tr = (x: number, y: number): A3D.Vec3 => A3D.vec3TransRot(A3D.vec3(x, y, 0), pos, rot); + return dxf3DFACE( + vec3tr(-half.x, -half.y), + vec3tr(half.x, -half.y), + vec3tr(half.x, half.y), + vec3tr(-half.x, half.y), + m.dxf + ); +} diff --git a/packages/abstract-3d/src/renderers/dxf/dxf-geometries/dxf-polygon.ts b/packages/abstract-3d/src/renderers/dxf/dxf-geometries/dxf-polygon.ts new file mode 100644 index 00000000..4b683a50 --- /dev/null +++ b/packages/abstract-3d/src/renderers/dxf/dxf-geometries/dxf-polygon.ts @@ -0,0 +1,36 @@ +import * as A3D from "../../../abstract-3d.js"; +import { dxf3DFACE } from "../dxf-encoding.js"; + +const chunkSize = 4; + +export function dxfPolygon(p: A3D.Polygon, m: A3D.Material, parentPos: A3D.Vec3, parentRot: A3D.Vec3): string { + let polygonString = ""; + const pos = A3D.vec3TransRot(p.pos, parentPos, parentRot); + const rot = A3D.vec3RotCombine(parentRot, p.rot); + const points = p.points.map((p) => A3D.vec3TransRot(p, pos, rot)); + let i = 0; + if (points.length >= chunkSize) { + for (i; i < points.length; i += chunkSize) { + polygonString += dxf3DFACE(points[i]!, points[i + 1]!, points[i + 2]!, points[i + 3]!, m.dxf); + } + } + + if (i <= points.length && chunkSize - 1 <= points.length) { + const lastArrayLength = points.length - i; + switch (lastArrayLength) { + case 1: + polygonString += dxf3DFACE(points[i - 2]!, points[i - 1]!, points[i]!, points[i]!, m.dxf); + break; + case 2: + polygonString += dxf3DFACE(points[i - 1]!, points[i]!, points[i + 1]!, points[i + 1]!, m.dxf); + break; + case 3: + polygonString += dxf3DFACE(points[i]!, points[i + 1]!, points[i + 2]!, points[i + 2]!, m.dxf); + break; + default: + break; + } + } + + return polygonString; +} diff --git a/packages/abstract-3d/src/renderers/dxf/dxf-geometries/dxf-shape.ts b/packages/abstract-3d/src/renderers/dxf/dxf-geometries/dxf-shape.ts new file mode 100644 index 00000000..8c07599c --- /dev/null +++ b/packages/abstract-3d/src/renderers/dxf/dxf-geometries/dxf-shape.ts @@ -0,0 +1,36 @@ +import * as A3D from "../../../abstract-3d.js"; +import { dxf3DFACE } from "../dxf-encoding.js"; + +const chunkSize = 4; + +export function dxfPolygon(s: A3D.Shape, m: A3D.Material, parentPos: A3D.Vec3, parentRot: A3D.Vec3): string { + let polygonString = ""; + const pos = A3D.vec3TransRot(s.pos, parentPos, parentRot); + const rot = A3D.vec3RotCombine(parentRot, s.rot); + const points = s.points.map((p) => A3D.vec3TransRot(A3D.vec3(p.x, p.y, 0), pos, rot)); + let i = 0; + if (points.length >= chunkSize) { + for (i; i < points.length; i += chunkSize) { + polygonString += dxf3DFACE(points[i]!, points[i + 1]!, points[i + 2]!, points[i + 3]!, m.dxf); + } + } + + if (i <= points.length && chunkSize - 1 <= points.length) { + const lastArrayLength = points.length - i; + switch (lastArrayLength) { + case 1: + polygonString += dxf3DFACE(points[i - 2]!, points[i - 1]!, points[i]!, points[i]!, m.dxf); + break; + case 2: + polygonString += dxf3DFACE(points[i - 1]!, points[i]!, points[i + 1]!, points[i + 1]!, m.dxf); + break; + case 3: + polygonString += dxf3DFACE(points[i]!, points[i + 1]!, points[i + 2]!, points[i + 2]!, m.dxf); + break; + default: + break; + } + } + + return polygonString; +} diff --git a/packages/abstract-3d/src/renderers/dxf/dxf.ts b/packages/abstract-3d/src/renderers/dxf/dxf.ts new file mode 100644 index 00000000..cc88b979 --- /dev/null +++ b/packages/abstract-3d/src/renderers/dxf/dxf.ts @@ -0,0 +1,38 @@ +import * as A3D from "../../abstract-3d.js"; +import { dxfFooter, dxfHeader } from "./dxf-encoding.js"; +import { dxfPlane } from "./dxf-geometries/dxf-plane.js"; +import { dxfBox } from "./dxf-geometries/dxf-box.js"; +import { dxfCylinder } from "./dxf-geometries/dxf-cylinder.js"; +import { dxfCone } from "./dxf-geometries/dxf-cone.js"; +import { dxfPolygon } from "./dxf-geometries/dxf-polygon.js"; +import { dimBoundZero, rotationForCameraPos, sizeCenterForCameraPos } from "../shared.js"; + +export const toDxf = (scene: A3D.Scene, view: A3D.View): string => { + const unitRot = A3D.vec3RotCombine(rotationForCameraPos(view), scene.rotation); + const rotatedCenter = A3D.vec3Rot(scene.center, A3D.vec3Zero, scene.rotation); + const [size, center] = sizeCenterForCameraPos(scene.size, rotatedCenter, dimBoundZero, A3D.vec3Zero, view, 1); + return dxfHeader(size, center) + scene.groups.reduce((a, c) => a + dxfGroup(c, center, unitRot), "") + dxfFooter; +}; + +function dxfGroup(g: A3D.Group, parentPos: A3D.Vec3, parentRot: A3D.Vec3): string { + const pos = A3D.vec3TransRot(g.pos, parentPos, parentRot); + const rot = A3D.vec3RotCombine(parentRot, g.rot); + return ( + g.meshes.reduce((a, c) => { + switch (c.geometry.type) { + case "Plane": + return a + dxfPlane(c.geometry, c.material, pos, rot); + case "Box": + return a + dxfBox(c.geometry, c.material, pos, rot); + case "Cylinder": + return a + dxfCylinder(c.geometry, c.material, 18, pos, rot); + case "Cone": + return a + dxfCone(c.geometry, c.material, 18, pos, rot); + case "Polygon": + return a + dxfPolygon(c.geometry, c.material, pos, rot); + default: + return a; + } + }, "") + g.groups.reduce((a, c) => a + dxfGroup(c, pos, rot), "") + ); +} diff --git a/packages/abstract-3d/src/renderers/dxf/index.ts b/packages/abstract-3d/src/renderers/dxf/index.ts new file mode 100644 index 00000000..29d57562 --- /dev/null +++ b/packages/abstract-3d/src/renderers/dxf/index.ts @@ -0,0 +1 @@ +export * from "./dxf.js"; diff --git a/packages/abstract-3d/src/renderers/index.ts b/packages/abstract-3d/src/renderers/index.ts new file mode 100644 index 00000000..5e35b139 --- /dev/null +++ b/packages/abstract-3d/src/renderers/index.ts @@ -0,0 +1,4 @@ +export * from "./dxf/index.js"; +export * from "./svg/index.js"; +export * from "./stl/index.js"; +export * from "./react/index.js"; diff --git a/packages/abstract-3d/src/renderers/react/index.ts b/packages/abstract-3d/src/renderers/react/index.ts new file mode 100644 index 00000000..52aca582 --- /dev/null +++ b/packages/abstract-3d/src/renderers/react/index.ts @@ -0,0 +1,5 @@ +export * from "./react.js"; +export type { HotSpotInfo } from "./react-hotspot.js"; +export type { CameraType, Camera, ControlsHelper } from "./react-camera.js"; +export type { MaterialState } from "./react-material.js"; +export { ERROR_IMG_KEY } from "./react-material.js"; diff --git a/packages/abstract-3d/src/renderers/react/react-camera.tsx b/packages/abstract-3d/src/renderers/react/react-camera.tsx new file mode 100644 index 00000000..93e24f83 --- /dev/null +++ b/packages/abstract-3d/src/renderers/react/react-camera.tsx @@ -0,0 +1,208 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import React, { useEffect, useRef, useState } from "react"; +// import { OrbitControls, OrbitControlsProps } from "@react-three/drei/core/OrbitControls.js"; +// import { GizmoHelper, GizmoHelperProps } from "@react-three/drei/core/GizmoHelper.js"; +// import { GizmoViewcube } from "@react-three/drei/core/GizmoViewcube.js"; +// import { OrthographicCamera } from "@react-three/drei/core/OrthographicCamera.js"; +// import { PerspectiveCamera } from "@react-three/drei/core/PerspectiveCamera.js"; +// import { GizmoViewport } from "@react-three/drei/core/GizmoViewport.js"; +import { + GizmoHelperProps, + PerspectiveCamera, + OrthographicCamera, + OrbitControlsProps, + OrbitControls, + GizmoHelper, + GizmoViewcube, + GizmoViewport, +} from "@react-three/drei"; +import { Vector3 } from "three"; +import { ThreeEvent, useFrame, useThree } from "@react-three/fiber"; +import { exhaustiveCheck } from "ts-exhaustive-check"; +import { View, Scene, Vec3, vec3 } from "../../abstract-3d.js"; + +export type Camera = A3dPerspectiveCamera | A3dOrthographicCamera; +export type CameraType = Camera["type"]; + +export type A3dPerspectiveCamera = { + readonly type: "Perspective"; + readonly near?: number; + readonly far?: number; + readonly fov?: number; +}; +export type A3dOrthographicCamera = { readonly type: "Orthographic"; readonly near?: number; readonly far?: number }; + +export type ControlsHelper = (Viewcube | Viewport) & { readonly props: Pick }; +type Viewcube = { readonly type: "Viewcube"; readonly viewcubeProps: GenericProps }; +type Viewport = { readonly type: "Viewport"; readonly viewportProps: GizmoViewportProps }; + +const ControlsWrapper = ( + props: OrbitControlsProps & { readonly setControls: (r: React.MutableRefObject) => void } +): JSX.Element => { + const ref = useRef(); + + useEffect(() => { + if (!ref.current) { + return; + } + props.setControls(ref.current); + }, [ref.current]); + return ; +}; + +export function ReactCamera({ + useAnimations, + camera, + view, + scene, + controlsHelper, + orbitContolsProps, +}: { + readonly useAnimations: boolean; + readonly camera: Camera; + readonly view: View; + readonly scene: Scene; + readonly controlsHelper?: ControlsHelper; + readonly orbitContolsProps?: OrbitControlsProps; +}): JSX.Element { + const [controls, setControls] = useState(null); + const perspectiveRef = useRef(undefined); + const orthographicRef = useRef(undefined); + const viewPortAspect = useThree(({ viewport: { aspect } }) => aspect); + + useEffect(() => { + const [posX, posY, posZ, size, sceneAspect] = (() => { + switch (view) { + case "front": + return [0, 0, 1, scene.size, scene.size.x / scene.size.y]; + case "back": + return [0, 0, -1, scene.size, scene.size.x / scene.size.y]; + case "top": + return [0, 1, 0, vec3(scene.size.x, scene.size.z, scene.size.y), scene.size.x / scene.size.z]; + case "bottom": + return [0, -1, 0, vec3(scene.size.x, scene.size.z, scene.size.y), scene.size.x / scene.size.z]; + case "right": + return [1, 0, 0, vec3(scene.size.z, scene.size.y, scene.size.x), scene.size.z / scene.size.y]; + case "left": + return [-1, 0, 0, vec3(scene.size.z, scene.size.y, scene.size.x), scene.size.z / scene.size.y]; + default: + return exhaustiveCheck(view); + } + })(); + + const dist = cameraDist(size, camera.type === "Perspective" ? camera.fov ?? 45 : 45); + + if (camera.type === "Orthographic" && orthographicRef.current) { + const [left, right, top, bottom] = + sceneAspect > viewPortAspect + ? [-size.x / 2, size.x / 2, size.x / 2 / viewPortAspect, -size.x / 2 / viewPortAspect] + : [(-viewPortAspect * size.y) / 2, (viewPortAspect * size.y) / 2, size.y / 2, -size.y / 2]; + orthographicRef.current.position.setX(posX * dist); + orthographicRef.current.position.setY(posY * dist); + orthographicRef.current.position.setZ(posZ * dist); + orthographicRef.current.left = left; + orthographicRef.current.right = right; + orthographicRef.current.bottom = bottom; + orthographicRef.current.top = top; + orthographicRef.current.updateProjectionMatrix(); + } else if (camera.type === "Perspective" && perspectiveRef.current) { + perspectiveRef.current.position.setX(posX * dist); + perspectiveRef.current.position.setY(posY * dist); + perspectiveRef.current.position.setZ(posZ * dist); + perspectiveRef.current.updateProjectionMatrix(); + } + }, [camera, viewPortAspect]); + // const prevScene = React.useRef(scene) + // useEffect(() => { + // prevScene.current = scene; + // }, [scene]); + + useFrame(() => { + // if (useAnimations && camera && prevScene.current !== scene) { + // const [, , z] = cameraDist(scene); + // vector3.set(camera.position.x, camera.position.y, z); + // camera.position.lerp(vector3, 0.12); + // ref.current.enabled = false; + // invalidate(); + // } else { + // ref.current.enabled = true; + // } + }); + return ( + <> + + + setControls(c.current)} /> + {(() => { + switch (controlsHelper?.type) { + case "Viewcube": + return ( + controls?.target as Vector3} + onUpdate={() => controls?.update?.()} + > + + + ); + case "Viewport": + return ( + controls?.target as Vector3} + onUpdate={() => controls?.update?.()} + > + + + ); + case undefined: + default: + return <>; + } + })()} + + ); +} + +type GizmoViewportProps = JSX.IntrinsicElements["group"] & { + readonly axisColors?: readonly [string, string, string]; + readonly axisScale?: readonly [number, number, number]; + readonly labels?: readonly [string, string, string]; + readonly axisHeadScale?: number; + readonly labelColor?: string; + readonly hideNegativeAxes?: boolean; + readonly hideAxisHeads?: boolean; + readonly disabled?: boolean; + readonly font?: string; + readonly onClick?: (e: ThreeEvent) => null; +}; + +type GenericProps = { + readonly font?: string; + readonly opacity?: number; + readonly color?: string; + readonly hoverColor?: string; + readonly textColor?: string; + readonly strokeColor?: string; + readonly onClick?: (e: ThreeEvent) => null; + readonly faces?: ReadonlyArray; +}; + +export const cameraDist = (size: Vec3, fov: number): number => + size.z * 0.5 + (size.x > size.y ? size.x : size.y) / (1 / 2 / Math.tan((Math.PI * fov) / 180 / 2)); diff --git a/packages/abstract-3d/src/renderers/react/react-dimension.tsx b/packages/abstract-3d/src/renderers/react/react-dimension.tsx new file mode 100644 index 00000000..39389a94 --- /dev/null +++ b/packages/abstract-3d/src/renderers/react/react-dimension.tsx @@ -0,0 +1,73 @@ +import { useFrame } from "@react-three/fiber"; +import React from "react"; +import { Group } from "three"; +import * as A3d from "../../abstract-3d.js"; +import { ReactMaterial } from "./react-material.js"; +import { ReactMesh } from "./react-mesh.js"; + +export const ReactDimensions = React.memo( + ({ + dimensions, + showDimensions, + }: { + readonly dimensions: A3d.Dimensions; + readonly showDimensions: boolean; + }): JSX.Element => { + const dimensionMaterial = React.useMemo(() => , []); + return ( + <> + {dimensions.dimensions.map((dimension, i) => ( + + {dimensionMaterial} + + ))} + + ); + } +); + +export function ReactDimension({ + d, + visible, + children, +}: { + readonly d: A3d.Dimension; + readonly visible: boolean; + readonly children: JSX.Element; +}): JSX.Element { + const ref = React.useRef(undefined!); + useFrame(({ camera }) => { + ref.current.visible = + visible && + ((): boolean => { + const cameraPositions = { + [camera.position.x >= 0 ? "right" : "left"]: true, + [camera.position.y >= 0 ? "top" : "bottom"]: true, + [camera.position.z >= 0 ? "front" : "back"]: true, + }; + if (d.views.every((cp) => cameraPositions[cp])) { + ref.current.position.set(d.pos.x, d.pos.y, d.pos.z); + ref.current.rotation.set(d.rot.x, d.rot.y, d.rot.z); + return true; + } + return false; + })(); + }); + return ( + + {children} + + ); +} + +const DimensionMeshes = React.memo( + ({ meshes, children }: { readonly meshes: ReadonlyArray; readonly children: JSX.Element }): JSX.Element => ( + <> + {meshes.map((m, i) => ( + + {children} + + ))} + + ) +); diff --git a/packages/abstract-3d/src/renderers/react/react-group.tsx b/packages/abstract-3d/src/renderers/react/react-group.tsx new file mode 100644 index 00000000..a36aeb1c --- /dev/null +++ b/packages/abstract-3d/src/renderers/react/react-group.tsx @@ -0,0 +1,128 @@ +import React from "react"; +import { Group } from "three"; +import { useFrame } from "@react-three/fiber"; +import * as A3d from "../../abstract-3d.js"; +import { MaterialState, ReactMaterial } from "./react-material.js"; +import { ReactMesh } from "./react-mesh.js"; + +export function ReactGroup({ + g, + materialStateImages, + hoveredId, + hoveredIdExternal, + selectedId, + hotSpotsActive, + activeComponents, + id, + rootData, + onClickGroup, + onContextMenuGroup, + setHoveredId, + createGroupKey, +}: { + readonly g: A3d.Group; + readonly materialStateImages?: Record; + readonly hoveredId: string | undefined; + readonly hoveredIdExternal: string | undefined; + readonly selectedId: string | undefined; + readonly hotSpotsActive: boolean; + readonly activeComponents: Record | undefined; + readonly id: string | undefined; + readonly rootData: Record; + readonly onClickGroup?: ( + id: string | undefined, + rootData: Record, + data: Record + ) => void; + readonly onContextMenuGroup?: ( + id: string, + rootData: Record, + data: Record, + left: number, + top: number + ) => void; + readonly setHoveredId: (id: string | undefined) => void; + readonly createGroupKey?: (g: A3d.Group, idx: number, rootData: Record, id: string) => string; +}): JSX.Element { + const ref = React.useRef(undefined!); + useFrame(({ invalidate }, delta) => { + if (g.animation) { + invalidate(); + ref.current.rotation.x += (g.animation.transform.rot.x / g.animation.duration) * 1000 * delta; + ref.current.rotation.y += (g.animation.transform.rot.y / g.animation.duration) * 1000 * delta; + ref.current.rotation.z += (g.animation.transform.rot.z / g.animation.duration) * 1000 * delta; + } else { + ref.current.rotation.x = g.rot.x; + ref.current.rotation.y = g.rot.y; + ref.current.rotation.z = g.rot.z; + ref.current.position.x = g.pos.x; + ref.current.position.y = g.pos.y; + ref.current.position.z = g.pos.z; + } + }); + const materialState = activeComponents?.[id ?? ""]; + const disabled = hotSpotsActive && materialState !== "Accept"; + return ( + { + if (onClickGroup) { + e.stopPropagation(); + onClickGroup(id, rootData, g.data); + } + }, + onPointerOver: (e) => { + e.stopPropagation(); + document.body.style.cursor = "pointer"; + setHoveredId(id); + }, + onPointerOut: (_e) => { + document.body.style.cursor = "auto"; + setHoveredId(undefined); + }, + onContextMenu: (e) => { + if (onContextMenuGroup) { + e.stopPropagation(); + onContextMenuGroup(id, rootData, g.data, e.nativeEvent.x, e.nativeEvent.y); + } + }, + })} + > + {g.groups.map((g, i) => ( + + ))} + {g.meshes.map((m, i) => ( + + + + ))} + + ); +} diff --git a/packages/abstract-3d/src/renderers/react/react-hotspot.tsx b/packages/abstract-3d/src/renderers/react/react-hotspot.tsx new file mode 100644 index 00000000..39a21f90 --- /dev/null +++ b/packages/abstract-3d/src/renderers/react/react-hotspot.tsx @@ -0,0 +1,116 @@ +import React from "react"; +// import { Html } from "@react-three/drei/web/Html.js"; +import { Html } from "@react-three/drei"; +import * as A3d from "../../abstract-3d.js"; +import { ReactMesh } from "./react-mesh.js"; +import { ReactMaterial } from "./react-material.js"; + +export interface HotSpotInfo { + readonly replaceId: string; + readonly replaceFromId: string; + readonly replaceOutlet: string; + readonly replaceToId: string; + readonly replaceInlet: string; +} + +export const ReactHotSpots = React.memo( + ({ + hotSpots, + showHotSpotTexts, + hotSpotTexts, + hotSpotZAdjPos, + activeHotSpots, + hoveredId, + onClickHotSpot, + setHoveredId, + }: { + readonly hotSpots: ReadonlyArray; + readonly showHotSpotTexts: boolean; + readonly hotSpotZAdjPos: number; + readonly hotSpotTexts?: Record; + readonly activeHotSpots: Record | undefined; + readonly hoveredId: string | undefined; + readonly onClickHotSpot?: (hotSpot: HotSpotInfo) => void; + readonly setHoveredId: (id: string | undefined) => void; + }): JSX.Element => { + return ( + <> + {hotSpots.map((h) => ( + + ))} + + ); + } +); + +export function ReactHotSpot({ + h, + hotSpotZAdjPos, + showHotSpotTexts, + hotSpotTexts, + activeHotSpots, + hoveredId, + onClickHotSpot, + setHoveredId, +}: { + readonly h: A3d.HotSpot; + readonly hotSpotZAdjPos: number; + readonly showHotSpotTexts: boolean; + readonly hotSpotTexts?: Record; + readonly activeHotSpots: Record | undefined; + readonly hoveredId: string | undefined; + readonly onClickHotSpot?: (hotSpot: HotSpotInfo) => void; + readonly setHoveredId: (id: string | undefined) => void; +}): JSX.Element { + const hotSpot = activeHotSpots ? activeHotSpots[h.id] : undefined; + const hsPos = h.mesh.geometry.type === "Box" ? h.mesh.geometry.pos : A3d.vec3Zero; + const text = hotSpotTexts?.[h.id]; + return ( + <> + { + if (onClickHotSpot) { + e.stopPropagation(); + onClickHotSpot(hotSpot); + } + }, + onPointerOver: (e) => { + e.stopPropagation(); + document.body.style.cursor = "pointer"; + setHoveredId(h.id); + }, + onPointerOut: (_e) => { + document.body.style.cursor = "auto"; + setHoveredId(undefined); + }, + onContextMenu: (e) => { + e.stopPropagation(); + }, + })} + > + + + + + {hotSpotTexts && text && ( + +
+ {text} +
+ + )} + + ); +} diff --git a/packages/abstract-3d/src/renderers/react/react-material.tsx b/packages/abstract-3d/src/renderers/react/react-material.tsx new file mode 100644 index 00000000..ea43ff7e --- /dev/null +++ b/packages/abstract-3d/src/renderers/react/react-material.tsx @@ -0,0 +1,168 @@ +import React from "react"; +import { Color, DoubleSide, MaterialParameters, SRGBColorSpace, Texture, TextureLoader } from "three"; +import { suspend } from "suspend-react"; +import * as A3d from "../../abstract-3d.js"; + +const decreasedOpacity = 0.2; + +export type MaterialState = "Accept" | "Error" | "Warning"; +export const ERROR_IMG_KEY = "error"; + +export function ReactMaterial({ + material, + id = "", + selectedId, + hoveredId, + disabled, + materialStateImages, + state, +}: { + readonly material: A3d.Material; + readonly id?: string; + readonly hoveredId?: string | undefined; + readonly selectedId?: string | undefined; + readonly disabled?: boolean; + readonly materialStateImages?: Record; + readonly state?: MaterialState | undefined; +}): JSX.Element { + const mat = + !state || material.image.type === "UrlImage" + ? material + : state === "Accept" + ? acceptMaterial + : state === "Error" + ? errorMaterial + : warningMaterial; + const color = selectedId === id ? mat.selected : hoveredId === id ? mat.hover : mat.normal; + + if (material.image.type === "UrlImage") { + return ( + + ); + } + + switch (mat.type) { + case "Basic": + return ( + + ); + case "Phong": + return ( + + ); + // return ( + // + // ); + case "Lambert": + default: + return ( + + ); + } +} + +function TextureMaterial({ + url, + color, + material, +}: { + readonly url: string; + readonly color: string | Color; + readonly material: A3d.Material; +}): JSX.Element { + const texture = suspend( + new Promise((res) => + textureLoader.load( + url, + (data) => { + data.colorSpace = SRGBColorSpace; + res(data); + }, + undefined, + () => res(null) + ) + ), + [url] + ) as Texture | null; + + return ( + + ); +} + +const textureLoader = new TextureLoader(); + +const materialDefaults: MaterialParameters = { transparent: false, opacity: 1.0, depthWrite: true, depthTest: true }; + +const acceptMaterial: A3d.Material = { + type: "Phong", + normal: "rgb(0,148,91)", + hover: "rgb(1,88,55)", + selected: "rgb(1,88,55)", + dxf: "0", + imageType: "", + image: { type: "NoImage" }, + opacity: 1.0, + shininess: 50, +}; + +const errorMaterial: A3d.Material = { + type: "Phong", + normal: "#b82f3a", + hover: "#991c31", + selected: "#991c31", + dxf: "0", + imageType: "", + image: { type: "NoImage" }, + opacity: 1.0, + shininess: 50, +}; + +const warningMaterial: A3d.Material = { + type: "Phong", + normal: "rgb(240, 197, 48)", + hover: "rgb(221, 181, 38)", + selected: "rgb(182, 147, 20)", + dxf: "0", + imageType: "", + image: { type: "NoImage" }, + opacity: 1.0, + shininess: 50, +}; diff --git a/packages/abstract-3d/src/renderers/react/react-mesh.tsx b/packages/abstract-3d/src/renderers/react/react-mesh.tsx new file mode 100644 index 00000000..e06500f7 --- /dev/null +++ b/packages/abstract-3d/src/renderers/react/react-mesh.tsx @@ -0,0 +1,380 @@ +/* eslint-disable functional/no-class */ +/* eslint-disable functional/no-this-expression */ +/* eslint-disable functional/prefer-readonly-type */ +import React from "react"; +// import { Text } from "@react-three/drei/core/Text.js"; +// import { Line } from "@react-three/drei/core/Line.js"; +import { Text, Line } from "@react-three/drei"; +import { + BoxGeometry, + CylinderGeometry, + ConeGeometry, + PlaneGeometry, + Shape, + Path, + ExtrudeGeometry, + Vector3, + Euler, + Quaternion, + TubeGeometry, + CatmullRomCurve3, + Curve, + BufferAttribute, + SphereGeometry, +} from "three"; +import { exhaustiveCheck } from "ts-exhaustive-check"; +import * as A3d from "../../abstract-3d.js"; +import { Hole } from "../../abstract-3d.js"; + +const boxGeometry = new BoxGeometry(); +const cylinderGeometry = new CylinderGeometry(1, 1, 1, 40, 1); +const coneGeometry = new ConeGeometry(1, 1, 16, 1); +const planeGeometry = new PlaneGeometry(); +const sphereGeometry = new SphereGeometry(1, 9, 9); +export const euler = new Euler(); +export const vector3 = new Vector3(); +export const quaternion = new Quaternion(); + +export function ReactMesh({ + mesh, + children, +}: { + readonly mesh: A3d.Mesh; + readonly children?: JSX.Element; +}): JSX.Element { + switch (mesh.geometry.type) { + case "Box": { + const { pos, size, rot, holes } = mesh.geometry; + return holes.length === 0 ? ( + + {children} + + ) : ( + + {children} + + ); + } + case "Cone": { + const { pos, radius, rot, length } = mesh.geometry; + return ( + + {children} + + ); + } + case "Cylinder": { + const { pos, radius, rot, length, holes } = mesh.geometry; + return holes.length === 0 ? ( + + {children} + + ) : ( + {children} + ); + } + case "Plane": { + const { pos, size, rot, holes } = mesh.geometry; + return holes.length === 0 ? ( + + {children} + + ) : ( + + {children} + + ); + } + case "Sphere": { + const { pos, radius } = mesh.geometry; + return ( + + {children} + + ); + } + case "Shape": + return {children}; + case "Text": { + const { pos, rot, text, fontSize } = mesh.geometry; + return ( + + {children} + {text} + + ); + } + case "Line": { + const { start, end, thickness } = mesh.geometry; + return ( + + {children} + + ); + } + case "Polygon": + return {children}; + case "Tube": + return {children}; + default: + return ; + } +} + +function ExcrudeBoxPlane({ + geo, + sizeZ, + children, +}: { + readonly geo: A3d.Box | A3d.Plane; + readonly sizeZ: number; + readonly children?: JSX.Element; +}): JSX.Element { + const half = A3d.vec2Scale(geo.size, 0.5); + + const excrudeGeometry = React.useMemo(() => { + const shape = new Shape(); + shape.moveTo(-half.x, -half.y).lineTo(-half.x, half.y).lineTo(half.x, half.y).lineTo(half.x, -half.y).closePath(); + holes(geo.holes, shape); + return new ExtrudeGeometry(shape, { depth: sizeZ, bevelEnabled: false }); + }, [geo]); + + return ( + // Doesn't seem to adjust for excrude z size directly??? + + + {children} + + + ); +} + +function ExcrudeShape({ s, children }: { readonly s: A3d.Shape; readonly children?: JSX.Element }): JSX.Element { + const excrudeGeometry = React.useMemo(() => { + const shape = new Shape(); + if (s.points.length > 0) { + const p = s.points[0]!; + shape.moveTo(p.x, p.y); + } + for (let i = 1; i < s.points.length; i++) { + const p = s.points[i]!; + shape.lineTo(p.x, p.y); + } + shape.closePath(); + holes(s.holes, shape); + return new ExtrudeGeometry(shape, { depth: s.thickness, bevelEnabled: false }); + }, [s]); + + return ( + // Doesn't seem to adjust for excrude z size directly??? + + + {children} + + + ); +} + +function ExcrudeCylinder({ + cyl, + children, +}: { + readonly cyl: A3d.Cylinder; + readonly children?: JSX.Element; +}): JSX.Element { + const excrudeGeometry = React.useMemo(() => { + const shape = new Shape(); + shape.moveTo(0, cyl.radius).absellipse(0, 0, cyl.radius, cyl.radius, 0, Math.PI * 2, true); + holes(cyl.holes, shape); + return new ExtrudeGeometry(shape, { depth: cyl.length, bevelEnabled: false }); + }, [cyl]); + + return ( + // Doesn't seem to adjust for excrude z size directly??? + + + {children} + + + ); +} + +function holes(holes: ReadonlyArray, shape: Shape): void { + holes.forEach((h) => { + switch (h.type) { + case "RoundHole": + shape.holes.push(new Path().absarc(h.pos.x, h.pos.y, h.radius, 0, Math.PI * 2, true)); + break; + case "SquareHole": { + const path = new Path(); + const halfHole = A3d.vec2Scale(h.size, 0.5); + const min = A3d.vec2Sub(h.pos, halfHole); + const max = A3d.vec2Add(h.pos, halfHole); + path.moveTo(min.x, min.y).lineTo(min.x, max.y).lineTo(max.x, max.y).lineTo(max.x, min.y).closePath(); + shape.holes.push(path); + break; + } + default: + exhaustiveCheck(h); + } + }); +} + +function Polygon({ + polygon, + children, +}: { + readonly polygon: A3d.Polygon; + readonly children?: JSX.Element; +}): JSX.Element { + const ref = React.useRef(undefined!); + const vertices = React.useMemo(() => { + let newVertices: Float32Array = undefined!; + switch (polygon.points.length) { + default: + case 3: { + newVertices = new Float32Array(polygon.points.length * 3); + let i = 0; + polygon.points.forEach((p) => { + newVertices[i++] = p.x; + newVertices[i++] = p.y; + newVertices[i++] = p.z; + }); + break; + } + case 4: { + const v0 = polygon.points[0]!; + const v1 = polygon.points[1]!; + const v2 = polygon.points[2]!; + const v3 = polygon.points[3]!; + newVertices = new Float32Array([ + v0.x, + v0.y, + v0.z, + v1.x, + v1.y, + v1.z, + v2.x, + v2.y, + v2.z, + v2.x, + v2.y, + v2.z, + v3.x, + v3.y, + v3.z, + v0.x, + v0.y, + v0.z, + ]); + break; + } + } + if (ref.current) { + ref.current.needsUpdate = true; + } + return newVertices; + }, [polygon]); + + return ( + + self.computeVertexNormals()}> + + + {children} + + ); +} + +function Tube({ tube, children }: { readonly tube: A3d.Tube; readonly children?: JSX.Element }): JSX.Element { + const tubeGeometry = React.useMemo(() => { + return new TubeGeometry( + tube.curve.type === "SplineCurve" + ? new CatmullRomCurve3(tube.curve.points.map((p) => new Vector3(p.x, p.y, p.z))) + : new CircleCurve(tube.curve), + 9, + tube.radius, + 9, + false + ); + }, [tube]); + + return ( + + {children} + + ); +} + +class CircleCurve extends Curve { + radius: number; + angleLength: number; + startAngle: number; + + constructor(circleCurve: A3d.CircleCurve) { + super(); + this.radius = circleCurve.radius; + this.startAngle = circleCurve.angleStart; + this.angleLength = circleCurve.angleLength; + } + + getPoint(t: number, optionalTarget = new Vector3()): Vector3 { + return optionalTarget.set( + -this.radius * Math.sin(this.startAngle + this.angleLength * t), + -this.radius * Math.cos(this.startAngle + this.angleLength * t), + 0 + ); + } +} diff --git a/packages/abstract-3d/src/renderers/react/react-scene.tsx b/packages/abstract-3d/src/renderers/react/react-scene.tsx new file mode 100644 index 00000000..04bba3ee --- /dev/null +++ b/packages/abstract-3d/src/renderers/react/react-scene.tsx @@ -0,0 +1,132 @@ +import React from "react"; +import { extend } from "@react-three/fiber"; +import { + Mesh, + Group, + MeshLambertMaterial, + MeshPhongMaterial, + MeshBasicMaterial, + BoxGeometry, + PlaneGeometry, + CylinderGeometry, + ConeGeometry, + Texture, + DoubleSide, + BufferAttribute, + Shape, + Path, + ExtrudeGeometry, +} from "three"; +import * as A3d from "../../abstract-3d.js"; +import { HotSpotInfo, ReactHotSpots } from "./react-hotspot.js"; +import { ReactDimensions } from "./react-dimension.js"; +import { ReactGroup } from "./react-group.js"; +import { MaterialState } from "./react-material.js"; + +extend({ + Mesh, + Group, + MeshLambertMaterial, + MeshPhongMaterial, + MeshBasicMaterial, + BufferAttribute, + Shape, + ExtrudeGeometry, + Path, + Texture, + CylinderGeometry, + BoxGeometry, + PlaneGeometry, + ConeGeometry, + DoubleSide, +}); + +export function ReactScene({ + scene, + selectedId, + hoveredIdExternal, + activeHotSpots, + activeComponents, + hotSpotTexts, + showHotSpotTexts, + showDimensions, + materialStateImages, + onClickGroup, + onContextMenuGroup, + onClickHotSpot, + createGroupKey, + createGroupId, +}: { + readonly scene: A3d.Scene; + readonly selectedId: string | undefined; + readonly hoveredIdExternal: string | undefined; + readonly activeHotSpots: Record | undefined; + readonly activeComponents: Record | undefined; + readonly showHotSpotTexts: boolean; + readonly showDimensions: boolean; + readonly hotSpotTexts?: Record; + readonly materialStateImages?: Record; + readonly onClickGroup?: ( + id: string | undefined, + rootData: Record, + data: Record + ) => void; + readonly onContextMenuGroup?: ( + id: string, + rootData: Record, + data: Record, + left: number, + top: number + ) => void; + readonly onClickHotSpot?: (hotSpot: HotSpotInfo) => void; + readonly createGroupKey?: (g: A3d.Group, idx: number, rootData: Record, id: string) => string; + readonly createGroupId?: (g: A3d.Group) => string; +}): JSX.Element { + const [hoveredId, setHoveredId] = React.useState(undefined); + return ( + + {scene.groups.map((g, i) => { + const id = createGroupId ? createGroupId(g) : ""; + return ( + + ); + })} + + + + + + + + ); +} diff --git a/packages/abstract-3d/src/renderers/react/react.tsx b/packages/abstract-3d/src/renderers/react/react.tsx new file mode 100644 index 00000000..1a95eae7 --- /dev/null +++ b/packages/abstract-3d/src/renderers/react/react.tsx @@ -0,0 +1,113 @@ +import React, { memo } from "react"; +import { Canvas, Props } from "@react-three/fiber"; +import { OrbitControlsProps } from "@react-three/drei"; +import { ReactScene } from "./react-scene.js"; +import * as A3d from "../../abstract-3d.js"; +import { ReactCamera, ControlsHelper, Camera } from "./react-camera.js"; +import { HotSpotInfo } from "./react-hotspot.js"; +import { MaterialState } from "./react-material.js"; + +export const toReact = memo( + ({ + scene, + selectedId, + activeHotSpots, + activeComponents, + hoveredIdExternal, + hotSpotTexts, + showHotSpotTexts = false, + showDimensions = true, + useAnimations = false, + camera = { type: "Perspective" }, + view = "front", + controlsHelper, + canvasProps, + orbitContolsProps, + materialStateImages, + onClickGroup, + onContextMenuGroup, + onClickHotSpot, + createGroupKey, + createGroupId, + }: { + readonly scene: A3d.Scene; + readonly selectedId?: string | undefined; + readonly activeHotSpots?: Record | undefined; + readonly activeComponents?: Record | undefined; + readonly hoveredIdExternal?: string | undefined; + readonly showHotSpotTexts?: boolean; + readonly showDimensions?: boolean; + readonly hotSpotTexts?: Record; + readonly useAnimations?: boolean; + readonly camera?: Camera; + readonly view?: A3d.View; + readonly controlsHelper?: ControlsHelper; + readonly canvasProps?: Omit, "children">; + readonly orbitContolsProps?: OrbitControlsProps & React.RefAttributes; + readonly materialStateImages?: Record; + readonly onClickGroup?: ( + id: string | undefined, + rootData: Record, + data: Record + ) => void; + readonly onContextMenuGroup?: ( + id: string, + rootData: Record, + data: Record, + left: number, + top: number + ) => void; + readonly onClickHotSpot?: (hotSpot: HotSpotInfo) => void; + readonly createGroupKey?: (g: A3d.Group, idx: number, rootData: Record, id: string) => string; + readonly createGroupId?: (g: A3d.Group) => string; + }): JSX.Element => { + return ( + + {/* */} + + + + + }> + + + + ); + } +); diff --git a/packages/abstract-3d/src/renderers/shared.ts b/packages/abstract-3d/src/renderers/shared.ts new file mode 100644 index 00000000..c01fa510 --- /dev/null +++ b/packages/abstract-3d/src/renderers/shared.ts @@ -0,0 +1,111 @@ +import { + Vec3, + View, + DimensionBounds, + vec3Add, + vec3, + vec3Sub, + vec3Scale, + vec3Rot, + bounds3FromVec3Array, + bounds3ToSize, + vec3Zero, + bounds3Zero, +} from "../abstract-3d.js"; + +export function sizeCenterForCameraPos( + size: Vec3, + center: Vec3, + _dimBound: DimensionBounds, + rotation: Vec3, + _view: View, + factor: number +): readonly [Vec3, Vec3] { + // const [sizeAdj, centerAdj] = (() => { + // switch (view) { + // case "front": + // default: + // return [ + // vec3Add(size, vec3(dimBound.front.min.x + dimBound.front.max.x, dimBound.front.min.y + dimBound.front.max.y, 0)), + // vec3Add(center, vec3((dimBound.front.max.x - dimBound.front.min.x) / 2, (dimBound.front.max.y - dimBound.front.min.y) / 2, 0)), + // ]; + // case "back": + // return [ + // vec3Add(size, vec3(dimBound.back.min.x + dimBound.back.max.x, dimBound.back.min.y + dimBound.back.max.y, 0)), + // vec3Add(center, vec3((dimBound.back.max.x - dimBound.back.min.x) / 2, (dimBound.back.max.y - dimBound.back.min.y) / 2, 0)), + // ]; + // case "top": + // return [ + // vec3Add(size, vec3(dimBound.top.min.x + dimBound.top.max.x, 0, dimBound.top.min.y + dimBound.top.max.y)), + // vec3Add(center, vec3((dimBound.top.max.x - dimBound.top.min.x) / 2, (dimBound.top.max.y - dimBound.top.min.y) / 2, 0)), + // ]; + // case "bottom": + // return [ + // vec3Add(size, vec3(dimBound.bottom.min.x + dimBound.bottom.max.x, 0, dimBound.bottom.min.y + dimBound.bottom.max.y)), + // vec3Add(center, vec3((dimBound.bottom.max.x - dimBound.bottom.min.x) / 2, (dimBound.bottom.max.y - dimBound.bottom.min.y) / 2, 0)), + // ]; + // case "right": + // return [ + // vec3Add(size, vec3(0, dimBound.right.min.y + dimBound.right.max.y, dimBound.right.min.x + dimBound.right.max.x)), + // vec3Add(center, vec3((dimBound.right.max.x - dimBound.right.min.x) / 2, (dimBound.right.max.y - dimBound.right.min.y) / 2, 0)), + // ]; + // case "left": + // return [ + // vec3Add(size, vec3(0, dimBound.left.min.y + dimBound.left.max.y, dimBound.left.min.x + dimBound.left.max.x)), + // vec3Add(center, vec3((dimBound.left.max.x - dimBound.left.min.x) / 2, (dimBound.left.max.y - dimBound.left.min.y) / 2, 0)), + // ]; + // } + // })(); + + const [sizeAdj, centerAdj] = (() => { + return [size, center]; + })(); + + const half = vec3Scale(sizeAdj, 0.5); + const min = vec3Sub(centerAdj, half); + const max = vec3Add(centerAdj, half); + const v1 = vec3Rot(vec3(min.x, min.y, max.z), centerAdj, rotation); + const v2 = vec3Rot(vec3(max.x, min.y, max.z), centerAdj, rotation); + const v3 = vec3Rot(vec3(max.x, max.y, max.z), centerAdj, rotation); + const v4 = vec3Rot(vec3(min.x, max.y, max.z), centerAdj, rotation); + const v5 = vec3Rot(vec3(min.x, min.y, min.z), centerAdj, rotation); + const v6 = vec3Rot(vec3(max.x, min.y, min.z), centerAdj, rotation); + const v7 = vec3Rot(vec3(max.x, max.y, min.z), centerAdj, rotation); + const v8 = vec3Rot(vec3(min.x, max.y, min.z), centerAdj, rotation); + const bounds = bounds3FromVec3Array([v1, v2, v3, v4, v5, v6, v7, v8]); + return [vec3Scale(bounds3ToSize(bounds), factor), vec3Scale(centerAdj, factor)]; +} + +export function rotationForCameraPos(view: View): Vec3 { + switch (view) { + default: + case "front": + return vec3Zero; + case "back": + return vec3(-Math.PI, 0, -Math.PI); + case "top": + return vec3(Math.PI / 2, 0, 0); + case "bottom": + return vec3(-Math.PI / 2, 0, 0); + case "right": + return vec3(0, -Math.PI / 2, 0); + case "left": + return vec3(0, Math.PI / 2, 0); + } +} + +export const dimBoundZero: DimensionBounds = { + front: bounds3Zero, + back: bounds3Zero, + top: bounds3Zero, + bottom: bounds3Zero, + right: bounds3Zero, + left: bounds3Zero, + threeD: bounds3Zero, +}; + +export function rgbGray(color: string): string { + const parts = color.split("(")[1]?.slice(0, -1).split(","); + const c = Number(parts?.[0] ?? 416) * 0.3 + Number(parts?.[1] ?? 212) * 0.587 + Number(parts?.[2] ?? 1100) * 0.114; + return `rgb(${c},${c},${c})`; +} diff --git a/packages/abstract-3d/src/renderers/stl/index.ts b/packages/abstract-3d/src/renderers/stl/index.ts new file mode 100644 index 00000000..fec6c485 --- /dev/null +++ b/packages/abstract-3d/src/renderers/stl/index.ts @@ -0,0 +1 @@ +export * from "./stl.js"; diff --git a/packages/abstract-3d/src/renderers/stl/stl-encoding.ts b/packages/abstract-3d/src/renderers/stl/stl-encoding.ts new file mode 100644 index 00000000..7a9a1a49 --- /dev/null +++ b/packages/abstract-3d/src/renderers/stl/stl-encoding.ts @@ -0,0 +1,22 @@ +import { Vec3, vec3Sub } from "../../abstract-3d.js"; + +export const stlTriangle = (vec1: Vec3, vec2: Vec3, vec3: Vec3): string => { + const v = vec3Sub(vec2, vec1); + const w = vec3Sub(vec3, vec1); + const nx = v.y * w.z - v.z * w.y; + const ny = v.z * w.x - v.x * w.z; + const nz = v.x * w.y - v.y * w.x; + const sum = Math.sqrt(nx * nx + ny * ny + nz * nz); + return `facet normal ${nx / sum} ${ny / sum} ${nz / sum} + outer loop + vertex ${vec1.x} ${vec1.y} ${vec1.z} + vertex ${vec2.x} ${vec2.y} ${vec2.z} + vertex ${vec3.x} ${vec3.y} ${vec3.z} + endloop +endfacet +`; +}; + +export function stlPlaneOfVertices(v1: Vec3, v2: Vec3, v3: Vec3, v4: Vec3): string { + return stlTriangle(v1, v2, v3) + stlTriangle(v3, v4, v1); +} diff --git a/packages/abstract-3d/src/renderers/stl/stl-geometries/stl-box.ts b/packages/abstract-3d/src/renderers/stl/stl-geometries/stl-box.ts new file mode 100644 index 00000000..82b616a6 --- /dev/null +++ b/packages/abstract-3d/src/renderers/stl/stl-geometries/stl-box.ts @@ -0,0 +1,33 @@ +import * as A3D from "../../../abstract-3d.js"; +import { stlTriangle } from "../stl-encoding.js"; + +export function stlBox(b: A3D.Box, _m: A3D.Material, parentPos: A3D.Vec3, parentRot: A3D.Vec3): string { + const half = A3D.vec3Scale(b.size, 0.5); + const pos = A3D.vec3TransRot(b.pos, parentPos, parentRot); + const rot = A3D.vec3RotCombine(parentRot, b.rot); + const vec3tr = (x: number, y: number, z: number): A3D.Vec3 => A3D.vec3TransRot(A3D.vec3(x, y, z), pos, rot); + + const v1 = vec3tr(-half.x, -half.y, half.z); + const v2 = vec3tr(half.x, -half.y, half.z); + const v3 = vec3tr(half.x, half.y, half.z); + const v4 = vec3tr(-half.x, half.y, half.z); + const v5 = vec3tr(-half.x, -half.y, -half.z); + const v6 = vec3tr(half.x, -half.y, -half.z); + const v7 = vec3tr(half.x, half.y, -half.z); + const v8 = vec3tr(-half.x, half.y, -half.z); + + return ( + stlTriangle(v1, v2, v3) + // front + stlTriangle(v3, v4, v1) + + stlTriangle(v5, v6, v7) + // Back + stlTriangle(v7, v8, v5) + + stlTriangle(v5, v1, v4) + // Left + stlTriangle(v8, v4, v5) + + stlTriangle(v6, v2, v3) + // Right + stlTriangle(v3, v7, v6) + + stlTriangle(v8, v7, v3) + // Top + stlTriangle(v3, v4, v8) + + stlTriangle(v5, v6, v2) + // Bottom + stlTriangle(v2, v1, v5) + ); +} diff --git a/packages/abstract-3d/src/renderers/stl/stl-geometries/stl-cone.ts b/packages/abstract-3d/src/renderers/stl/stl-geometries/stl-cone.ts new file mode 100644 index 00000000..97c1e000 --- /dev/null +++ b/packages/abstract-3d/src/renderers/stl/stl-geometries/stl-cone.ts @@ -0,0 +1,37 @@ +import * as A3D from "../../../abstract-3d.js"; +import { stlPlaneOfVertices } from "../stl-encoding.js"; + +export function stlCone( + c: A3D.Cone, + _m: A3D.Material, + sides: number, + parentPos: A3D.Vec3, + parentRot: A3D.Vec3 +): string { + let dxfString = ""; + const pos = A3D.vec3TransRot(c.pos, parentPos, parentRot); + const rot = A3D.vec3RotCombine(parentRot, c.rot); + const vec3tr = (x: number, y: number, z: number): A3D.Vec3 => A3D.vec3TransRot(A3D.vec3(x, y, z), pos, rot); + + const angleStep = (2 * Math.PI) / sides; + let currentAngle = 0; + + const half = c.length / 2; + const botPos = vec3tr(0, -half, 0); + const topPos = vec3tr(0, half, 0); + + const botVec3Array = Array(); + for (let i = 0; i <= sides; i++) { + const currBot = vec3tr(Math.sin(currentAngle) * c.radius, -half, Math.cos(currentAngle) * c.radius); + botVec3Array.push(currBot); + + if (i !== 0) { + const prevBot = botVec3Array[i - 1]!; + dxfString += + stlPlaneOfVertices(botPos, prevBot, currBot, currBot) + stlPlaneOfVertices(currBot, prevBot, topPos, topPos); + } + currentAngle += angleStep; + } + + return dxfString; +} diff --git a/packages/abstract-3d/src/renderers/stl/stl-geometries/stl-cylinder.ts b/packages/abstract-3d/src/renderers/stl/stl-geometries/stl-cylinder.ts new file mode 100644 index 00000000..4514a3ee --- /dev/null +++ b/packages/abstract-3d/src/renderers/stl/stl-geometries/stl-cylinder.ts @@ -0,0 +1,45 @@ +import * as A3D from "../../../abstract-3d.js"; +import { stlPlaneOfVertices } from "../stl-encoding.js"; + +export function stlCylinder( + c: A3D.Cylinder, + _m: A3D.Material, + sides: number, + parentPos: A3D.Vec3, + parentRot: A3D.Vec3 +): string { + let dxfString = ""; + const pos = A3D.vec3TransRot(c.pos, parentPos, parentRot); + const rot = A3D.vec3RotCombine(parentRot, c.rot); + const vec3tr = (x: number, y: number, z: number): A3D.Vec3 => A3D.vec3TransRot(A3D.vec3(x, y, z), pos, rot); + + const angleStep = (2 * Math.PI) / sides; + let currentAngle = 0; + + const half = c.length / 2; + const topPos = vec3tr(0, half, 0); + const botPos = vec3tr(0, -half, 0); + + const botVec3Array = Array(); + const topVec3Array = Array(); + + for (let i = 0; i <= sides; i++) { + const x = Math.sin(currentAngle) * c.radius; + const z = Math.cos(currentAngle) * c.radius; + const currBot = vec3tr(x, -half, z); + const currTop = vec3tr(x, half, z); + botVec3Array.push(currBot); + topVec3Array.push(currTop); + if (i !== 0) { + const prevBot = botVec3Array[i - 1]!; + const prevTop = topVec3Array[i - 1]!; + dxfString += + stlPlaneOfVertices(botPos, prevBot, currBot, currBot) + + stlPlaneOfVertices(topPos, prevTop, currTop, currTop) + + stlPlaneOfVertices(currBot, prevBot, prevTop, currTop); + } + currentAngle += angleStep; + } + + return dxfString; +} diff --git a/packages/abstract-3d/src/renderers/stl/stl-geometries/stl-plane.ts b/packages/abstract-3d/src/renderers/stl/stl-geometries/stl-plane.ts new file mode 100644 index 00000000..b312a950 --- /dev/null +++ b/packages/abstract-3d/src/renderers/stl/stl-geometries/stl-plane.ts @@ -0,0 +1,16 @@ +import * as A3D from "../../../abstract-3d.js"; +import { stlTriangle } from "../stl-encoding.js"; + +export function stlPlane(p: A3D.Plane, _m: A3D.Material, parentPos: A3D.Vec3, parentRot: A3D.Vec3): string { + const half = A3D.vec2Scale(p.size, 0.5); + const pos = A3D.vec3TransRot(p.pos, parentPos, parentRot); + const rot = A3D.vec3RotCombine(parentRot, p.rot); + const vec3tr = (x: number, y: number): A3D.Vec3 => A3D.vec3TransRot(A3D.vec3(x, y, 0), pos, rot); + const [v1, v2, v3, v4] = [ + vec3tr(-half.x, -half.y), + vec3tr(half.x, -half.y), + vec3tr(half.x, half.y), + vec3tr(-half.x, half.y), + ]; + return stlTriangle(v1, v2, v3) + stlTriangle(v3, v4, v1); +} diff --git a/packages/abstract-3d/src/renderers/stl/stl-geometries/stl-polygon.ts b/packages/abstract-3d/src/renderers/stl/stl-geometries/stl-polygon.ts new file mode 100644 index 00000000..3f9ff2ab --- /dev/null +++ b/packages/abstract-3d/src/renderers/stl/stl-geometries/stl-polygon.ts @@ -0,0 +1,35 @@ +import * as A3D from "../../../abstract-3d.js"; +import { stlTriangle } from "../stl-encoding.js"; + +const chunkSize = 3; + +export function stlPolygon(p: A3D.Polygon, _m: A3D.Material, parentPos: A3D.Vec3, parentRot: A3D.Vec3): string { + let polygonString = ""; + const pos = A3D.vec3TransRot(p.pos, parentPos, parentRot); + const rot = A3D.vec3RotCombine(parentRot, p.rot); + const points = p.points.map((p) => A3D.vec3TransRot(p, pos, rot)); + let i = 0; + const nbrIterations = Math.floor(points.length / chunkSize); + for (i; i < nbrIterations; i++) { + const start = i * chunkSize; + polygonString += stlTriangle(points[start]!, points[start + 1]!, points[start + 2]!); + } + + const usedPoints = i * chunkSize; + + if (usedPoints <= points.length && chunkSize - 1 <= points.length) { + const lastArrayLength = points.length - usedPoints; + switch (lastArrayLength) { + case 1: + polygonString += stlTriangle(points[usedPoints - 1]!, points[usedPoints]!, points[0]!); + break; + case 2: + polygonString += stlTriangle(points[usedPoints]!, points[usedPoints + 1]!, points[0]!); + break; + default: + break; + } + } + + return polygonString; +} diff --git a/packages/abstract-3d/src/renderers/stl/stl-geometries/stl-shape.ts b/packages/abstract-3d/src/renderers/stl/stl-geometries/stl-shape.ts new file mode 100644 index 00000000..77e63e56 --- /dev/null +++ b/packages/abstract-3d/src/renderers/stl/stl-geometries/stl-shape.ts @@ -0,0 +1,35 @@ +import * as A3D from "../../../abstract-3d.js"; +import { stlTriangle } from "../stl-encoding.js"; + +const chunkSize = 3; + +export function stlShape(s: A3D.Shape, _m: A3D.Material, parentPos: A3D.Vec3, parentRot: A3D.Vec3): string { + let polygonString = ""; + const pos = A3D.vec3TransRot(s.pos, parentPos, parentRot); + const rot = A3D.vec3RotCombine(parentRot, s.rot); + const points = s.points.map((p) => A3D.vec3TransRot(A3D.vec3(p.x, p.y, 0), pos, rot)); + let i = 0; + const nbrIterations = Math.floor(points.length / chunkSize); + for (i; i < nbrIterations; i++) { + const start = i * chunkSize; + polygonString += stlTriangle(points[start]!, points[start + 1]!, points[start + 2]!); + } + + const usedPoints = i * chunkSize; + + if (usedPoints <= points.length && chunkSize - 1 <= points.length) { + const lastArrayLength = points.length - usedPoints; + switch (lastArrayLength) { + case 1: + polygonString += stlTriangle(points[usedPoints - 1]!, points[usedPoints]!, points[0]!); + break; + case 2: + polygonString += stlTriangle(points[usedPoints]!, points[usedPoints + 1]!, points[0]!); + break; + default: + break; + } + } + + return polygonString; +} diff --git a/packages/abstract-3d/src/renderers/stl/stl.ts b/packages/abstract-3d/src/renderers/stl/stl.ts new file mode 100644 index 00000000..31ecc4ea --- /dev/null +++ b/packages/abstract-3d/src/renderers/stl/stl.ts @@ -0,0 +1,33 @@ +import * as A3D from "../../abstract-3d.js"; +import { stlPlane } from "./stl-geometries/stl-plane.js"; +import { stlBox } from "./stl-geometries/stl-box.js"; +import { stlCylinder } from "./stl-geometries/stl-cylinder.js"; +import { stlCone } from "./stl-geometries/stl-cone.js"; +import { stlPolygon } from "./stl-geometries/stl-polygon.js"; + +export const toStl = (scene: A3D.Scene): string => + `solid +` + scene.groups.reduce((a, c) => a + stlGroup(c, scene.center, scene.rotation), ""); + +function stlGroup(g: A3D.Group, parentPos: A3D.Vec3, parentRot: A3D.Vec3): string { + const pos = A3D.vec3TransRot(g.pos, parentPos, parentRot); + const rot = A3D.vec3RotCombine(parentRot, g.rot); + return ( + g.meshes.reduce((a, m) => { + switch (m.geometry.type) { + case "Plane": + return a + stlPlane(m.geometry, m.material, pos, rot); + case "Box": + return a + stlBox(m.geometry, m.material, pos, rot); + case "Cylinder": + return a + stlCylinder(m.geometry, m.material, 18, pos, rot); + case "Cone": + return a + stlCone(m.geometry, m.material, 18, pos, rot); + case "Polygon": + return a + stlPolygon(m.geometry, m.material, pos, rot); + default: + return a; + } + }, "") + g.groups.reduce((a, c) => a + stlGroup(c, pos, rot), "") + ); +} diff --git a/packages/abstract-3d/src/renderers/svg/index.ts b/packages/abstract-3d/src/renderers/svg/index.ts new file mode 100644 index 00000000..df1e2f8a --- /dev/null +++ b/packages/abstract-3d/src/renderers/svg/index.ts @@ -0,0 +1,2 @@ +export * from "./svg.js"; +export type { EmbededImage } from "./svg-encoding.js"; diff --git a/packages/abstract-3d/src/renderers/svg/svg-encoding.ts b/packages/abstract-3d/src/renderers/svg/svg-encoding.ts new file mode 100644 index 00000000..5074b465 --- /dev/null +++ b/packages/abstract-3d/src/renderers/svg/svg-encoding.ts @@ -0,0 +1,46 @@ +import { Vec2, vec2Add, vec2Scale } from "../../abstract-3d.js"; + +export const svg = (width: number, height: number, children: string): string => + `${children} `; + +export const svgLine = (p1: Vec2, p2: Vec2, stroke: string, strokeWidth: number): string => + ``; + +export const svgPolygon = (points: ReadonlyArray, fill: string, stroke: string, strokeWidth: number): string => + ``; + +export const svgText = (p: Vec2, text: string, rot: number, color: string, font: string, fontSize: number): string => + `${text}`; + +export type EmbededImage = + | { readonly type: "url"; readonly url: string } + | { readonly type: "svg"; readonly svg: string }; + +export const svgImage = (p: Vec2, size: Vec2, rot: number, data: EmbededImage): string => { + const half = vec2Scale(size, 0.5); + return data.type === "url" + ? `` + : `${data.svg} + + `; +}; + +const transformOrigin = (p: Vec2, half: Vec2): string => + `transform-origin="${(p.x + half.x).toFixed(0)}px ${(p.y + half.y).toFixed(0)}px"`; + +const rotate = (rot: number): string => `rotate(${rot.toFixed(0)})`; + +const translate = (p: Vec2): string => `translate(${p.x.toFixed(0)}, ${p.y.toFixed(0)})`; diff --git a/packages/abstract-3d/src/renderers/svg/svg-geometries/shared.ts b/packages/abstract-3d/src/renderers/svg/svg-geometries/shared.ts new file mode 100644 index 00000000..ff49cedc --- /dev/null +++ b/packages/abstract-3d/src/renderers/svg/svg-geometries/shared.ts @@ -0,0 +1,10 @@ +export type zOrderElement = { readonly element: string; readonly zOrder: number }; +export const zElem = (element: string, zOrder: number): zOrderElement => ({ element, zOrder }); + +export const stBW = 0.7; +export const stN = 0.1; + +export const gray = "rgb(85, 85, 85)"; +export const white = "rgb(255, 255, 255)"; +export const transparent = "rgba(255, 255, 255, 0)"; +export const black = "rgb(15, 15, 15)"; diff --git a/packages/abstract-3d/src/renderers/svg/svg-geometries/svg-box.ts b/packages/abstract-3d/src/renderers/svg/svg-geometries/svg-box.ts new file mode 100644 index 00000000..69d362da --- /dev/null +++ b/packages/abstract-3d/src/renderers/svg/svg-geometries/svg-box.ts @@ -0,0 +1,61 @@ +import * as A3D from "../../../abstract-3d.js"; +import { gray, black, zElem, zOrderElement } from "./shared.js"; +import { svgPolygon } from "../svg-encoding.js"; +import { rgbGray } from "../../shared.js"; + +export function box( + b: A3D.Box, + point: (x: number, y: number) => A3D.Vec2, + color: string, + onlyStroke: boolean, + grayScale: boolean, + stroke: number, + onlyStrokeFill: string, + parentPos: A3D.Vec3, + parentRot: A3D.Vec3 +): ReadonlyArray { + const half = A3D.vec3Scale(b.size, 0.5); + const pos = A3D.vec3TransRot(b.pos, parentPos, parentRot); + const rot = A3D.vec3RotCombine(parentRot, b.rot); + const vec3tr = (p: A3D.Vec3): A3D.Vec3 => A3D.vec3TransRot(p, pos, rot); + + const v1 = vec3tr(A3D.vec3(-half.x, -half.y, half.z)); + const v2 = vec3tr(A3D.vec3(half.x, -half.y, half.z)); + const v3 = vec3tr(A3D.vec3(half.x, half.y, half.z)); + const v4 = vec3tr(A3D.vec3(-half.x, half.y, half.z)); + const v5 = vec3tr(A3D.vec3(-half.x, -half.y, -half.z)); + const v6 = vec3tr(A3D.vec3(half.x, -half.y, -half.z)); + const v7 = vec3tr(A3D.vec3(half.x, half.y, -half.z)); + const v8 = vec3tr(A3D.vec3(-half.x, half.y, -half.z)); + + const frontMean = A3D.vec3ZMean(v1, v2, v3, v4); + const backMean = A3D.vec3ZMean(v5, v6, v7, v8); + const [frontBackPoints, frontBackMean] = + frontMean > backMean + ? [[point(v1.x, v1.y), point(v2.x, v2.y), point(v3.x, v3.y), point(v4.x, v4.y)], frontMean] + : [[point(v5.x, v5.y), point(v6.x, v6.y), point(v7.x, v7.y), point(v8.x, v8.y)], backMean]; + + const topMean = A3D.vec3ZMean(v8, v7, v3, v4); + const botMean = A3D.vec3ZMean(v5, v6, v2, v1); + const [topBotPoints, topBotMean] = + topMean > botMean + ? [[point(v8.x, v8.y), point(v7.x, v7.y), point(v3.x, v3.y), point(v4.x, v4.y)], topMean] + : [[point(v5.x, v5.y), point(v6.x, v6.y), point(v2.x, v2.y), point(v1.x, v1.y)], botMean]; + + const rightMean = A3D.vec3ZMean(v6, v2, v3, v7); + const leftMean = A3D.vec3ZMean(v5, v1, v4, v8); + const [rightLeftPoints, rightLeftMean] = + rightMean > leftMean + ? [[point(v6.x, v6.y), point(v2.x, v2.y), point(v3.x, v3.y), point(v7.x, v7.y)], rightMean] + : [[point(v5.x, v5.y), point(v1.x, v1.y), point(v4.x, v4.y), point(v8.x, v8.y)], leftMean]; + + const [strokeColor, fill, strokeUse] = onlyStroke + ? [grayScale ? gray : color, onlyStrokeFill, stroke] + : [black, grayScale ? rgbGray(color) : color, 0]; + + return [ + zElem(svgPolygon(frontBackPoints, fill, strokeColor, strokeUse), frontBackMean), + zElem(svgPolygon(topBotPoints, fill, strokeColor, strokeUse), topBotMean), + zElem(svgPolygon(rightLeftPoints, fill, strokeColor, strokeUse), rightLeftMean), + ]; +} diff --git a/packages/abstract-3d/src/renderers/svg/svg-geometries/svg-cone.ts b/packages/abstract-3d/src/renderers/svg/svg-geometries/svg-cone.ts new file mode 100644 index 00000000..795ca4ba --- /dev/null +++ b/packages/abstract-3d/src/renderers/svg/svg-geometries/svg-cone.ts @@ -0,0 +1,52 @@ +import * as A3D from "../../../abstract-3d.js"; +import { gray, stBW, zElem, zOrderElement, transparent } from "./shared.js"; +import { svgPolygon } from "../svg-encoding.js"; +import { rgbGray } from "../../shared.js"; + +export function cone( + c: A3D.Cone, + point: (x: number, y: number) => A3D.Vec2, + color: string, + onlyStroke: boolean, + grayScale: boolean, + _stroke: number, + onlyStrokeFill: string, + parentPos: A3D.Vec3, + parentRot: A3D.Vec3 +): ReadonlyArray { + const half = A3D.vec3(c.radius, c.length / 2, c.radius); + const pos = A3D.vec3TransRot(c.pos, parentPos, parentRot); + const rot = A3D.vec3RotCombine(parentRot, c.rot); + const vec3tr = (p: A3D.Vec3): A3D.Vec3 => A3D.vec3TransRot(p, pos, rot); + + const [stroke, fill] = onlyStroke + ? [grayScale ? gray : color, onlyStrokeFill] + : [transparent, grayScale ? rgbGray(color) : color]; + const zOrderComponents = Array(); + + const sides = 8; + const angleStep = (2 * Math.PI) / sides; + let currentAngle = 0; + + const topPos = vec3tr(A3D.vec3(0, half.y, 0)); + const botVec3Array = Array(); + for (let i = 0; i <= sides; i++) { + const currBot = vec3tr( + A3D.vec3(0 + Math.sin(currentAngle) * c.radius, -half.y, 0 + Math.cos(currentAngle) * c.radius) + ); + botVec3Array.push(currBot); + if (i !== 0) { + const prevBot = botVec3Array[i - 1]!; + const points = [ + point(currBot.x, currBot.y), + point(prevBot.x, prevBot.y), + point(topPos.x, topPos.y), + point(topPos.x, topPos.y), + ]; + zOrderComponents.push(zElem(svgPolygon(points, fill, stroke, stBW), A3D.vec3ZMean(currBot, prevBot, topPos))); + } + currentAngle += angleStep; + } + + return zOrderComponents; +} diff --git a/packages/abstract-3d/src/renderers/svg/svg-geometries/svg-cylinder.ts b/packages/abstract-3d/src/renderers/svg/svg-geometries/svg-cylinder.ts new file mode 100644 index 00000000..32dfc59c --- /dev/null +++ b/packages/abstract-3d/src/renderers/svg/svg-geometries/svg-cylinder.ts @@ -0,0 +1,58 @@ +import * as A3D from "../../../abstract-3d.js"; +import { gray, stBW, transparent, zElem, zOrderElement } from "./shared.js"; +import { svgPolygon } from "../svg-encoding.js"; +import { rgbGray } from "../../shared.js"; + +export function cylinder( + c: A3D.Cylinder, + point: (x: number, y: number) => A3D.Vec2, + color: string, + onlyStroke: boolean, + grayScale: boolean, + _stroke: number, + onlyStrokeFill: string, + parentPos: A3D.Vec3, + parentRot: A3D.Vec3 +): ReadonlyArray { + const half = A3D.vec3(c.radius, c.length / 2, c.radius); + const pos = A3D.vec3TransRot(c.pos, parentPos, parentRot); + const rot = A3D.vec3RotCombine(parentRot, c.rot); + const vec3tr = (p: A3D.Vec3): A3D.Vec3 => A3D.vec3TransRot(p, pos, rot); + + const [stroke, fill] = onlyStroke + ? [grayScale ? gray : color, onlyStrokeFill] + : [transparent, grayScale ? rgbGray(color) : color]; + const zOrderComponents = Array(); + + const sides = 8; + const angleStep = (2 * Math.PI) / sides; + let currentAngle = 0; + + const botVec3Array = Array(); + const topVec3Array = Array(); + for (let i = 0; i <= sides; i++) { + const x = Math.sin(currentAngle) * c.radius; + const z = Math.cos(currentAngle) * c.radius; + const currBot = vec3tr(A3D.vec3(x, -half.y, z)); + const currTop = vec3tr(A3D.vec3(x, half.y, z)); + botVec3Array.push(currBot); + topVec3Array.push(currTop); + if (i !== 0) { + const prevBot = botVec3Array[i - 1]!; + const prevTop = topVec3Array[i - 1]!; + + const points = [ + point(currBot.x, currBot.y), + point(prevBot.x, prevBot.y), + point(prevTop.x, prevTop.y), + point(currTop.x, currTop.y), + ]; + zOrderComponents.push( + zElem(svgPolygon(points, fill, stroke, stBW), A3D.vec3ZMean(currBot, prevBot, currTop, prevTop)) + ); + } + currentAngle += angleStep; + } + + return zOrderComponents; +} diff --git a/packages/abstract-3d/src/renderers/svg/svg-geometries/svg-line.ts b/packages/abstract-3d/src/renderers/svg/svg-geometries/svg-line.ts new file mode 100644 index 00000000..ade5d5c2 --- /dev/null +++ b/packages/abstract-3d/src/renderers/svg/svg-geometries/svg-line.ts @@ -0,0 +1,20 @@ +import * as A3D from "../../../abstract-3d.js"; +import { zElem, zOrderElement } from "./shared.js"; +import { svgLine } from "../svg-encoding.js"; +import { rgbGray } from "../../shared.js"; + +export function line( + l: A3D.Line, + point: (x: number, y: number) => A3D.Vec2, + fill: string, + grayScale: boolean, + _stroke: number, + parentPos: A3D.Vec3, + parentRot: A3D.Vec3 +): ReadonlyArray { + const v1 = A3D.vec3TransRot(l.start, parentPos, parentRot); + const v2 = A3D.vec3TransRot(l.end, parentPos, parentRot); + return [ + zElem(svgLine(point(v1.x, v1.y), point(v2.x, v2.y), grayScale ? rgbGray(fill) : fill, 0.5), A3D.vec3ZMean(v1, v2)), + ]; +} diff --git a/packages/abstract-3d/src/renderers/svg/svg-geometries/svg-plane.ts b/packages/abstract-3d/src/renderers/svg/svg-geometries/svg-plane.ts new file mode 100644 index 00000000..59712f4d --- /dev/null +++ b/packages/abstract-3d/src/renderers/svg/svg-geometries/svg-plane.ts @@ -0,0 +1,45 @@ +import * as A3D from "../../../abstract-3d.js"; +import { gray, black, zElem, zOrderElement } from "./shared.js"; +import { EmbededImage, svgImage, svgPolygon } from "../svg-encoding.js"; +import { rgbGray } from "../../shared.js"; + +export function plane( + p: A3D.Plane, + point: (x: number, y: number) => A3D.Vec2, + color: string, + onlyStroke: boolean, + grayScale: boolean, + _stroke: number, + onlyStrokeFill: string, + parentPos: A3D.Vec3, + parentRot: A3D.Vec3, + view: A3D.View, + image?: EmbededImage | undefined, + _imageType?: "svg" | "png" | undefined +): ReadonlyArray { + const half = A3D.vec2Scale(p.size, 0.5); + const pos = A3D.vec3TransRot(p.pos, parentPos, parentRot); + const rot = A3D.vec3RotCombine(parentRot, p.rot); + const vec3tr = (point: A3D.Vec3): A3D.Vec3 => A3D.vec3TransRot(point, pos, rot); + + const v2 = vec3tr(A3D.vec3(half.x, -half.y, 0)); + const v4 = vec3tr(A3D.vec3(-half.x, half.y, 0)); + + if (view === "front" && image) { + const [leftX, rightX] = v4.x > v2.x ? [v2.x, v4.x] : [v4.x, v2.x]; + const [bottomY, topY] = v4.y > v2.y ? [v4.y, v2.y] : [v2.y, v4.y]; + const bottomLeft = point(leftX, bottomY); + const topRight = point(rightX, topY); + const degrees = A3D.isZero(rot.z, 0.1) ? 0 : rot.z * (-180 / Math.PI); + const img = svgImage(bottomLeft, A3D.vec2(topRight.x - bottomLeft.x, topRight.y - bottomLeft.y), degrees, image); + return [zElem(img, (v2.z + v4.z) / 2)]; + } + + const v1 = vec3tr(A3D.vec3(-half.x, -half.y, 0)); + const v3 = vec3tr(A3D.vec3(half.x, half.y, 0)); + const points = [point(v1.x, v1.y), point(v2.x, v2.y), point(v3.x, v3.y), point(v4.x, v4.y)]; + const [stroke, fill, strokeThickness] = onlyStroke + ? [grayScale ? gray : color, onlyStrokeFill, _stroke] + : [black, grayScale ? rgbGray(color) : color, 0]; + return [zElem(svgPolygon(points, fill, stroke, strokeThickness), A3D.vec3ZMean(v1, v2, v3, v4))]; +} diff --git a/packages/abstract-3d/src/renderers/svg/svg-geometries/svg-polygon.ts b/packages/abstract-3d/src/renderers/svg/svg-geometries/svg-polygon.ts new file mode 100644 index 00000000..36f36873 --- /dev/null +++ b/packages/abstract-3d/src/renderers/svg/svg-geometries/svg-polygon.ts @@ -0,0 +1,25 @@ +import { Polygon, vec3ZMean, Vec3, Vec2, vec3TransRot, vec3RotCombine } from "../../../abstract-3d.js"; +import { gray, zElem, zOrderElement, transparent } from "./shared.js"; +import { svgPolygon } from "../svg-encoding.js"; +import { rgbGray } from "../../shared.js"; + +export function polygon( + p: Polygon, + point: (x: number, y: number) => Vec2, + color: string, + onlyStroke: boolean, + grayScale: boolean, + onlyStrokeFill: string, + _stroke: number, + parentPos: Vec3, + parentRot: Vec3 +): ReadonlyArray { + const pos = vec3TransRot(p.pos, parentPos, parentRot); + const rot = vec3RotCombine(parentRot, p.rot); + const rotatedPoints = p.points.map((p) => vec3TransRot(p, pos, rot)); + const points = rotatedPoints.map(({ x, y }) => point(x, y)); + const [strokeColor, fill] = onlyStroke + ? [grayScale ? gray : color, onlyStrokeFill] + : [transparent, grayScale ? rgbGray(color) : color]; + return [zElem(svgPolygon(points, fill, strokeColor, 0), vec3ZMean(...rotatedPoints))]; +} diff --git a/packages/abstract-3d/src/renderers/svg/svg-geometries/svg-shape.ts b/packages/abstract-3d/src/renderers/svg/svg-geometries/svg-shape.ts new file mode 100644 index 00000000..93eeb9a8 --- /dev/null +++ b/packages/abstract-3d/src/renderers/svg/svg-geometries/svg-shape.ts @@ -0,0 +1,25 @@ +import { vec3ZMean, Vec3, Vec2, vec3TransRot, vec3RotCombine, Shape, vec3 } from "../../../abstract-3d.js"; +import { gray, zElem, zOrderElement, transparent } from "./shared.js"; +import { svgPolygon } from "../svg-encoding.js"; +import { rgbGray } from "../../shared.js"; + +export function shape( + s: Shape, + point: (x: number, y: number) => Vec2, + color: string, + onlyStroke: boolean, + grayScale: boolean, + onlyStrokeFill: string, + _stroke: number, + parentPos: Vec3, + parentRot: Vec3 +): ReadonlyArray { + const pos = vec3TransRot(s.pos, parentPos, parentRot); + const rot = vec3RotCombine(parentRot, s.rot); + const rotatedPoints = s.points.map((p) => vec3TransRot(vec3(p.x, p.y, 0), pos, rot)); + const points = rotatedPoints.map(({ x, y }) => point(x, y)); + const [strokeColor, fill] = onlyStroke + ? [grayScale ? gray : color, onlyStrokeFill] + : [transparent, grayScale ? rgbGray(color) : color]; + return [zElem(svgPolygon(points, fill, strokeColor, 0), vec3ZMean(...rotatedPoints))]; +} diff --git a/packages/abstract-3d/src/renderers/svg/svg-geometries/svg-text.ts b/packages/abstract-3d/src/renderers/svg/svg-geometries/svg-text.ts new file mode 100644 index 00000000..24f89de3 --- /dev/null +++ b/packages/abstract-3d/src/renderers/svg/svg-geometries/svg-text.ts @@ -0,0 +1,28 @@ +import * as A3D from "../../../abstract-3d.js"; +import { zElem, zOrderElement } from "./shared.js"; +import { svgText } from "../svg-encoding.js"; + +export function text( + t: A3D.Text, + point: (x: number, y: number) => A3D.Vec2, + fill: string, + parentPos: A3D.Vec3, + parentRot: A3D.Vec3, + factor: number, + font: string +): ReadonlyArray { + const pos = A3D.vec3TransRot(t.pos, parentPos, parentRot); + const rot = A3D.vec3RotCombine(parentRot, t.rot); + const texts = Array(); + const degrees = A3D.isZero(rot.z, 0.1) ? 0 : rot.z * (-180 / Math.PI); + + const fontSize = t.fontSize * factor; + const strings = t.text.split("\n"); + let posY = strings.length === 1 ? 0 : (fontSize * strings.length - fontSize) / 2; + for (const s of strings) { + texts.push(zElem(svgText(point(pos.x, pos.y + posY), s, degrees, fill, font, fontSize), pos.z)); + posY -= fontSize * 1.2; + } + + return texts; +} diff --git a/packages/abstract-3d/src/renderers/svg/svg.ts b/packages/abstract-3d/src/renderers/svg/svg.ts new file mode 100644 index 00000000..1183fdb7 --- /dev/null +++ b/packages/abstract-3d/src/renderers/svg/svg.ts @@ -0,0 +1,204 @@ +import { exhaustiveCheck } from "ts-exhaustive-check"; +import { + vec2, + vec3Scale, + Scene, + View, + Vec3, + Mesh, + vec3Rot, + vec3Zero, + vec3RotCombine, + vec3, + Vec2, + Group, + vec3TransRot, +} from "../../abstract-3d.js"; +import { zOrderElement } from "./svg-geometries/shared.js"; +import { box } from "./svg-geometries/svg-box.js"; +import { cylinder } from "./svg-geometries/svg-cylinder.js"; +import { line } from "./svg-geometries/svg-line.js"; +import { plane } from "./svg-geometries/svg-plane.js"; +import { shape } from "./svg-geometries/svg-shape.js"; +import { polygon } from "./svg-geometries/svg-polygon.js"; +import { text } from "./svg-geometries/svg-text.js"; +import { cone } from "./svg-geometries/svg-cone.js"; +import { rotationForCameraPos, sizeCenterForCameraPos } from "../shared.js"; +import { EmbededImage, svg } from "./svg-encoding.js"; + +export function toSvg( + scene: Scene, + view: View, + onlyStroke: boolean, + grayScale: boolean, + stroke: number, + onlyStrokeFill: string = "rgba(255,255,255,0)", + font: string = "", + buffers?: Record, + scale?: { readonly size: number; readonly scaleByWidth: boolean } +): { readonly image: string; readonly width: number; readonly height: number } { + const factor = scale + ? scale.size / + (scale.scaleByWidth + ? view === "right" || view === "left" + ? scene.size.z + : scene.size.x + : view === "top" || view === "bottom" + ? scene.size.z + : scene.size.y) + : 1; + const unitRot = vec3RotCombine(rotationForCameraPos(view), scene.rotation); + const unitPos = vec3Rot(scene.center, vec3Zero, scene.rotation); + const [size, center] = sizeCenterForCameraPos(scene.size, unitPos, scene.dimensions.bounds, unitRot, view, factor); + const unitHalfSize = vec3Scale(size, 0.5); + const centerAdj = vec3(center.x - stroke * 0.75, center.y + stroke * 0.75, center.z); + const width = size.x + 1.5 * stroke; + const height = size.y + 1.5 * stroke; + const elements = Array(); + const point = (x: number, y: number): Vec2 => + vec2(-centerAdj.x + unitHalfSize.x + x * factor, centerAdj.y + unitHalfSize.y - y * factor); + for (const g of scene.groups) { + const pos = vec3Rot(g.pos, unitPos, unitRot); + const rot = vec3RotCombine(unitRot, g.rot); + elements.push( + ...svgGroup(g, pos, rot, point, view, factor, onlyStroke, grayScale, onlyStrokeFill, font, stroke, buffers) + ); + } + elements.sort((a, b) => a.zOrder - b.zOrder); + + for (const d of scene.dimensions?.dimensions ?? []) { + if (d.views[0] === view) { + const pos = vec3Rot(d.pos, unitPos, unitRot); + const rot = vec3RotCombine(unitRot, d.rot); + for (const m of d.meshes) { + elements.push( + ...svgMesh( + m, + pos, + rot, + point, + view, + factor, + scene.dimensions.material.normal, + false, + false, + onlyStrokeFill, + font, + stroke + ) + ); + } + } + } + + const image = svg( + width, + height, + elements.reduce((a, { element }) => `${a} ${element}`, "") + ); + return { image, width, height }; +} + +function svgGroup( + g: Group, + pos: Vec3, + rot: Vec3, + point: (x: number, y: number) => Vec2, + view: View, + factor: number, + onlyStroke: boolean, + grayScale: boolean, + onlyStrokeFill: string, + font: string, + stroke: number, + buffers?: Record +): ReadonlyArray { + const elements = Array(); + + for (const m of g.meshes) { + elements.push( + ...svgMesh( + m, + pos, + rot, + point, + view, + factor, + m.material.normal, + onlyStroke, + grayScale, + onlyStrokeFill, + font, + stroke, + m.material.image.type === "HashImage" && buffers?.[m.material.image.hash] + ? { type: "svg", svg: buffers[m.material.image.hash]! } + : m.material.image.type === "UrlImage" + ? { type: "url", url: m.material.image.url } + : undefined, + m.material.imageType as "svg" | "png" + ) + ); + } + for (const sg of g.groups) { + const sPos = vec3TransRot(sg.pos, pos, rot); + const sRot = vec3RotCombine(rot, sg.rot); + elements.push( + ...svgGroup(sg, sPos, sRot, point, view, factor, onlyStroke, grayScale, onlyStrokeFill, font, stroke, buffers) + ); + } + return elements; +} + +function svgMesh( + mesh: Mesh, + parentPos: Vec3, + parentRot: Vec3, + point: (x: number, y: number) => Vec2, + view: View, + factor: number, + color: string, + onlyStroke: boolean, + grayScale: boolean, + background: string, + font: string, + stroke: number, + image?: EmbededImage | undefined, + imageType?: "svg" | "png" | undefined +): ReadonlyArray { + switch (mesh.geometry.type) { + case "Box": + return box(mesh.geometry, point, color, onlyStroke, grayScale, stroke, background, parentPos, parentRot); + case "Plane": + return plane( + mesh.geometry, + point, + color, + onlyStroke, + grayScale, + stroke, + background, + parentPos, + parentRot, + view, + image, + imageType + ); + case "Cylinder": + return cylinder(mesh.geometry, point, color, onlyStroke, grayScale, stroke, background, parentPos, parentRot); + case "Cone": + return cone(mesh.geometry, point, color, onlyStroke, grayScale, stroke, background, parentPos, parentRot); + case "Line": + return line(mesh.geometry, point, color, grayScale, stroke, parentPos, parentRot); + case "Polygon": + return polygon(mesh.geometry, point, color, onlyStroke, grayScale, background, stroke, parentPos, parentRot); + case "Shape": + return shape(mesh.geometry, point, color, onlyStroke, grayScale, background, stroke, parentPos, parentRot); + case "Text": + return text(mesh.geometry, point, color, parentPos, parentRot, factor, font); + case "Tube": + case "Sphere": + return []; + default: + return exhaustiveCheck(mesh.geometry); + } +} diff --git a/packages/abstract-3d/tsconfig.json b/packages/abstract-3d/tsconfig.json new file mode 100644 index 00000000..7514301a --- /dev/null +++ b/packages/abstract-3d/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../tsconfig.settings.json", + "include": ["src"], + "compilerOptions": { + "outDir": "lib", + "rootDir": "src", + "lib": ["dom", "es2022"] + }, + "references": [] +} diff --git a/packages/abstract-visuals-example/src/app/abstract-3d-react-example-svg.tsx b/packages/abstract-visuals-example/src/app/abstract-3d-react-example-svg.tsx new file mode 100644 index 00000000..017a1444 --- /dev/null +++ b/packages/abstract-visuals-example/src/app/abstract-3d-react-example-svg.tsx @@ -0,0 +1,9 @@ +import * as React from "react"; +// import * as A3D from "../../../abstract-3d/lib/index.js"; + +export function Abstract3DReactExample(): JSX.Element { + return <>; + // return A3D.toReact({ + // scene: { center: A3D.vec3Zero, data: {}, groups: [], size: A3D.vec3Zero }, + // }); +} diff --git a/packages/abstract-visuals-example/src/app/container.tsx b/packages/abstract-visuals-example/src/app/container.tsx index 43c2db59..ad9419c9 100644 --- a/packages/abstract-visuals-example/src/app/container.tsx +++ b/packages/abstract-visuals-example/src/app/container.tsx @@ -1,12 +1,13 @@ import * as React from "react"; import { AbstractImageExampleReact } from "./abstract-image-example-react"; -import { AbstractImageExampleSvg } from "./abstract-image-example-svg"; +import { Abstract3DReactExample } from "./abstract-3d-react-example-svg"; import { AbstractImageExampleDxf } from "./abstract-image-example-dxf"; import { AbstractChartExample } from "./abstract-chart-example"; import { AbstractDocumentExample } from "./abstract-document-example"; import { AbstractDocumentXMLExample } from "./abstract-document-xml-example"; +import { AbstractImageExampleSvg } from "./abstract-image-example-svg"; -type Example = typeof examples[number]; +type Example = (typeof examples)[number]; const examples = [ "AbstractChart", @@ -15,6 +16,7 @@ const examples = [ "AbstractImageDxf", "AbstractDocument", "AbstractDocumentXML", + "Abstract3D", ] as const; export function Container(): JSX.Element { @@ -52,6 +54,8 @@ export function Container(): JSX.Element { return ; case "AbstractDocumentXML": return ; + case "Abstract3D": + return ; default: return <>; } diff --git a/packages/tsconfig.json b/packages/tsconfig.json index 405c84f1..b0baa74e 100644 --- a/packages/tsconfig.json +++ b/packages/tsconfig.json @@ -5,6 +5,7 @@ { "path": "abstract-chart" }, { "path": "abstract-document" }, { "path": "abstract-image" }, + { "path": "abstract-3d" }, { "path": "abstract-visuals-example" }, { "path": "test-utils" } ] diff --git a/yarn.lock b/yarn.lock index 65ddd5e7..f3eea8e3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -267,6 +267,13 @@ core-js "^2.6.5" regenerator-runtime "^0.13.4" +"@babel/runtime@^7.11.2": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.6.tgz#5b76eb89ad45e2e4a0a8db54c456251469a3358e" + integrity sha512-Ja18XcETdEl5mzzACGd+DKgaGJzPTCow7EglgwTmHdwokzDFYh/MHua6lU6DV/hjF2IaOJ4oX2nqnjG7RElKOw== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/runtime@^7.12.5", "@babel/runtime@^7.17.8": version "7.24.5" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.5.tgz#230946857c053a36ccc66e1dd03b17dd0c4ed02c" @@ -1544,6 +1551,11 @@ npmlog "^4.1.2" write-file-atomic "^2.3.0" +"@mediapipe/tasks-vision@0.10.8": + version "0.10.8" + resolved "https://registry.yarnpkg.com/@mediapipe/tasks-vision/-/tasks-vision-0.10.8.tgz#a78e137018a19933b7a1d0e887d553d4ab833d10" + integrity sha512-Rp7ll8BHrKB3wXaRFKhrltwZl1CiXGdibPxuWXvqGnKTnv8fqa/nvftYNuSbf+pbJWKYCXdBtYTITdAUTGGh0Q== + "@mrmlnc/readdir-enhanced@^2.2.1": version "2.2.1" resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde" @@ -1695,6 +1707,99 @@ resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== +"@react-spring/animated@~9.6.1": + version "9.6.1" + resolved "https://registry.yarnpkg.com/@react-spring/animated/-/animated-9.6.1.tgz#ccc626d847cbe346f5f8815d0928183c647eb425" + integrity sha512-ls/rJBrAqiAYozjLo5EPPLLOb1LM0lNVQcXODTC1SMtS6DbuBCPaKco5svFUQFMP2dso3O+qcC4k9FsKc0KxMQ== + dependencies: + "@react-spring/shared" "~9.6.1" + "@react-spring/types" "~9.6.1" + +"@react-spring/core@~9.6.1": + version "9.6.1" + resolved "https://registry.yarnpkg.com/@react-spring/core/-/core-9.6.1.tgz#ebe07c20682b360b06af116ea24e2b609e778c10" + integrity sha512-3HAAinAyCPessyQNNXe5W0OHzRfa8Yo5P748paPcmMowZ/4sMfaZ2ZB6e5x5khQI8NusOHj8nquoutd6FRY5WQ== + dependencies: + "@react-spring/animated" "~9.6.1" + "@react-spring/rafz" "~9.6.1" + "@react-spring/shared" "~9.6.1" + "@react-spring/types" "~9.6.1" + +"@react-spring/rafz@~9.6.1": + version "9.6.1" + resolved "https://registry.yarnpkg.com/@react-spring/rafz/-/rafz-9.6.1.tgz#d71aafb92b78b24e4ff84639f52745afc285c38d" + integrity sha512-v6qbgNRpztJFFfSE3e2W1Uz+g8KnIBs6SmzCzcVVF61GdGfGOuBrbjIcp+nUz301awVmREKi4eMQb2Ab2gGgyQ== + +"@react-spring/shared@~9.6.1": + version "9.6.1" + resolved "https://registry.yarnpkg.com/@react-spring/shared/-/shared-9.6.1.tgz#4e2e4296910656c02bd9fd54c559702bc836ac4e" + integrity sha512-PBFBXabxFEuF8enNLkVqMC9h5uLRBo6GQhRMQT/nRTnemVENimgRd+0ZT4yFnAQ0AxWNiJfX3qux+bW2LbG6Bw== + dependencies: + "@react-spring/rafz" "~9.6.1" + "@react-spring/types" "~9.6.1" + +"@react-spring/three@~9.6.1": + version "9.6.1" + resolved "https://registry.yarnpkg.com/@react-spring/three/-/three-9.6.1.tgz#095fcd1dc6509127c33c14486d88289b89baeb9d" + integrity sha512-Tyw2YhZPKJAX3t2FcqvpLRb71CyTe1GvT3V+i+xJzfALgpk10uPGdGaQQ5Xrzmok1340DAeg2pR/MCfaW7b8AA== + dependencies: + "@react-spring/animated" "~9.6.1" + "@react-spring/core" "~9.6.1" + "@react-spring/shared" "~9.6.1" + "@react-spring/types" "~9.6.1" + +"@react-spring/types@~9.6.1": + version "9.6.1" + resolved "https://registry.yarnpkg.com/@react-spring/types/-/types-9.6.1.tgz#913d3a68c5cbc1124fdb18eff919432f7b6abdde" + integrity sha512-POu8Mk0hIU3lRXB3bGIGe4VHIwwDsQyoD1F394OK7STTiX9w4dG3cTLljjYswkQN+hDSHRrj4O36kuVa7KPU8Q== + +"@react-three/drei@9.88.17": + version "9.88.17" + resolved "https://registry.yarnpkg.com/@react-three/drei/-/drei-9.88.17.tgz#c997c4eca8df5f1de3eb8cff83ae77652cadbd98" + integrity sha512-WEYAkikzw0juG3F1aMy1AEeuRmsGmKytb8ETZARd4E6Q61Z1ceoL7fRvrnrXE/A2MHKgSzJgkckMEhKlbbJlyw== + dependencies: + "@babel/runtime" "^7.11.2" + "@mediapipe/tasks-vision" "0.10.8" + "@react-spring/three" "~9.6.1" + "@use-gesture/react" "^10.2.24" + camera-controls "^2.4.2" + cross-env "^7.0.3" + detect-gpu "^5.0.28" + glsl-noise "^0.0.0" + lodash.clamp "^4.0.3" + lodash.omit "^4.5.0" + lodash.pick "^4.4.0" + maath "^0.9.0" + meshline "^3.1.6" + react-composer "^5.0.3" + react-merge-refs "^1.1.0" + stats-gl "^1.0.4" + stats.js "^0.17.0" + suspend-react "^0.1.3" + three-mesh-bvh "^0.6.7" + three-stdlib "^2.28.0" + troika-three-text "^0.47.2" + utility-types "^3.10.0" + uuid "^9.0.1" + zustand "^3.5.13" + +"@react-three/fiber@^8.16.6": + version "8.16.8" + resolved "https://registry.yarnpkg.com/@react-three/fiber/-/fiber-8.16.8.tgz#4d2fecda7b38f534de6bdac49ca37c815cf9a4ef" + integrity sha512-Lc8fjATtvQEfSd8d5iKdbpHtRm/aPMeFj7jQvp6TNHfpo8IQTW3wwcE1ZMrGGoUH+w2mnyS+0MK1NLPLnuzGkQ== + dependencies: + "@babel/runtime" "^7.17.8" + "@types/react-reconciler" "^0.26.7" + "@types/webxr" "*" + base64-js "^1.5.1" + buffer "^6.0.3" + its-fine "^1.0.6" + react-reconciler "^0.27.0" + react-use-measure "^2.1.1" + scheduler "^0.21.0" + suspend-react "^0.1.3" + zustand "^3.7.1" + "@sinclair/typebox@^0.27.8": version "0.27.8" resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" @@ -1943,6 +2048,11 @@ resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== +"@tweenjs/tween.js@~23.1.1": + version "23.1.2" + resolved "https://registry.yarnpkg.com/@tweenjs/tween.js/-/tween.js-23.1.2.tgz#4e5357fd6742f5aa50447d3fa808aed4cda93ed7" + integrity sha512-kMCNaZCJugWI86xiEHaY338CU5JpD0B97p1j1IKNn/Zto8PgACjQx0UxbHjmOcLl/dDOBnItwD07KmCs75pxtQ== + "@types/aria-query@^5.0.1": version "5.0.4" resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-5.0.4.tgz#1a31c3d378850d2778dabb6374d036dcba4ba708" @@ -2028,6 +2138,11 @@ resolved "https://registry.yarnpkg.com/@types/doctrine/-/doctrine-0.0.3.tgz#e892d293c92c9c1d3f9af72c15a554fbc7e0895a" integrity sha512-w5jZ0ee+HaPOaX25X2/2oGR/7rgAQSYII7X7pp0m9KgBfMP7uKfMfTvcpl5Dj+eDBbpxKGiqE+flqDr6XTd2RA== +"@types/draco3d@^1.4.0": + version "1.4.10" + resolved "https://registry.yarnpkg.com/@types/draco3d/-/draco3d-1.4.10.tgz#63ec0ba78b30bd58203ec031f4e4f0198c596dca" + integrity sha512-AX22jp8Y7wwaBgAixaSvkoG4M/+PlAcm3Qs4OW8yT9DM4xUpWKeFhLueTAyZF39pviAdcDdeJoACapiAceqNcw== + "@types/emscripten@^1.39.6": version "1.39.12" resolved "https://registry.yarnpkg.com/@types/emscripten/-/emscripten-1.39.12.tgz#e43b4fdd4b389861897d6cbb9665532f3afd5abd" @@ -2233,6 +2348,11 @@ resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz#56e2cc26c397c038fab0e3a917a12d5c5909e901" integrity sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA== +"@types/offscreencanvas@^2019.6.4": + version "2019.7.3" + resolved "https://registry.yarnpkg.com/@types/offscreencanvas/-/offscreencanvas-2019.7.3.tgz#90267db13f64d6e9ccb5ae3eac92786a7c77a516" + integrity sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A== + "@types/parse-json@^4.0.0": version "4.0.2" resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.2.tgz#5950e50960793055845e956c427fc2b0d70c5239" @@ -2267,6 +2387,20 @@ dependencies: "@types/react" "*" +"@types/react-reconciler@^0.26.7": + version "0.26.7" + resolved "https://registry.yarnpkg.com/@types/react-reconciler/-/react-reconciler-0.26.7.tgz#0c4643f30821ae057e401b0d9037e03e8e9b2a36" + integrity sha512-mBDYl8x+oyPX/VBb3E638N0B7xG+SPk/EAMcVPeexqus/5aTpTphQi0curhhshOqRrc9t6OPoJfEUkbymse/lQ== + dependencies: + "@types/react" "*" + +"@types/react-reconciler@^0.28.0", "@types/react-reconciler@^0.28.8": + version "0.28.8" + resolved "https://registry.yarnpkg.com/@types/react-reconciler/-/react-reconciler-0.28.8.tgz#e51710572bcccf214306833c2438575d310b3e98" + integrity sha512-SN9c4kxXZonFhbX4hJrZy37yw9e7EIxcpHCxQv5JUS18wDE5ovkQKlqQEkufdJCCMfuI9BnjUJvhYeJ9x5Ra7g== + dependencies: + "@types/react" "*" + "@types/react@*", "@types/react@^18.3.2": version "18.3.2" resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.2.tgz#462ae4904973bc212fa910424d901e3d137dbfcd" @@ -2284,6 +2418,14 @@ "@types/scheduler" "^0.16" csstype "^3.0.2" +"@types/react@^18.3.3": + version "18.3.3" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.3.tgz#9679020895318b0915d7a3ab004d92d33375c45f" + integrity sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw== + dependencies: + "@types/prop-types" "*" + csstype "^3.0.2" + "@types/retry@0.12.2": version "0.12.2" resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.2.tgz#ed279a64fa438bb69f2480eda44937912bb7480a" @@ -2335,6 +2477,22 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== +"@types/stats.js@*": + version "0.17.3" + resolved "https://registry.yarnpkg.com/@types/stats.js/-/stats.js-0.17.3.tgz#705446e12ce0fad618557dd88236f51148b7a935" + integrity sha512-pXNfAD3KHOdif9EQXZ9deK82HVNaXP5ZIF5RP2QG6OQFNTaY2YIetfrE9t528vEreGQvEPRDDc8muaoYeK0SxQ== + +"@types/three@^0.164.1": + version "0.164.1" + resolved "https://registry.yarnpkg.com/@types/three/-/three-0.164.1.tgz#ae9f1246e005fb005bc36a6136567f1e848c0e30" + integrity sha512-dR/trWDhyaNqJV38rl1TonlCA9DpnX7OPYDWD81bmBGn/+uEc3+zNalFxQcV4FlPTeDBhCY3SFWKvK6EJwL88g== + dependencies: + "@tweenjs/tween.js" "~23.1.1" + "@types/stats.js" "*" + "@types/webxr" "*" + fflate "~0.8.2" + meshoptimizer "~0.18.1" + "@types/tough-cookie@*": version "4.0.5" resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.5.tgz#cb6e2a691b70cb177c6e3ae9c1d2e8b2ea8cd304" @@ -2345,6 +2503,11 @@ resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.8.tgz#7545ba4fc3c003d6c756f651f3bf163d8f0f29ba" integrity sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA== +"@types/webxr@*", "@types/webxr@^0.5.2": + version "0.5.16" + resolved "https://registry.yarnpkg.com/@types/webxr/-/webxr-0.5.16.tgz#28955aa2d1197d1ef0b9826ae0f7e68f7eca0601" + integrity sha512-0E0Cl84FECtzrB4qG19TNTqpunw0F1YF0QZZnFMF6pDw1kNKJtrlTKlVB34stGIsHbZsYQ7H0tNjPfZftkHHoA== + "@types/ws@^8.5.10": version "8.5.10" resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.10.tgz#4acfb517970853fa6574a3a6886791d04a396787" @@ -2503,6 +2666,18 @@ resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== +"@use-gesture/core@10.3.1": + version "10.3.1" + resolved "https://registry.yarnpkg.com/@use-gesture/core/-/core-10.3.1.tgz#976c9421e905f0079d49822cfd5c2e56b808fc56" + integrity sha512-WcINiDt8WjqBdUXye25anHiNxPc0VOrlT8F6LLkU6cycrOGUDyY/yyFmsg3k8i5OLvv25llc0QC45GhR/C8llw== + +"@use-gesture/react@^10.2.24": + version "10.3.1" + resolved "https://registry.yarnpkg.com/@use-gesture/react/-/react-10.3.1.tgz#17a743a894d9bd9a0d1980c618f37f0164469867" + integrity sha512-Yy19y6O2GJq8f7CHf7L0nxL8bf4PZCPaVOCgJrusOeFHY1LvHgYXnmnXg6N5iwAnbgbZCDjo60SiM6IPJi9C5g== + dependencies: + "@use-gesture/core" "10.3.1" + "@webassemblyjs/ast@1.12.1", "@webassemblyjs/ast@^1.12.1": version "1.12.1" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.12.1.tgz#bb16a0e8b1914f979f45864c23819cc3e3f0d4bb" @@ -3300,6 +3475,13 @@ before-after-hook@^2.0.0: resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.2.3.tgz#c51e809c81a4e354084422b9b26bad88249c517c" integrity sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ== +bidi-js@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/bidi-js/-/bidi-js-1.0.3.tgz#6f8bcf3c877c4d9220ddf49b9bb6930c88f877d2" + integrity sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw== + dependencies: + require-from-string "^2.0.2" + binary-extensions@^2.0.0: version "2.3.0" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" @@ -3677,6 +3859,11 @@ camelcase@^6.2.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== +camera-controls@^2.4.2: + version "2.8.4" + resolved "https://registry.yarnpkg.com/camera-controls/-/camera-controls-2.8.4.tgz#df2935a4309beb0d4fd5170589a08552ba2e755c" + integrity sha512-pzVKpeZCRXIx2VOMB+E4OPjOhErHqhxrHYxcRLofOVgBeCeKSb8QAC2toc1onMllrxldRWXR8bl4K50hkrtwsg== + caniuse-lite@^1.0.30001587: version "1.0.30001620" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001620.tgz#78bb6f35b8fe315b96b8590597094145d0b146b4" @@ -4299,6 +4486,13 @@ create-jest@^29.7.0: jest-util "^29.7.0" prompts "^2.0.1" +cross-env@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-7.0.3.tgz#865264b29677dc015ba8418918965dd232fc54cf" + integrity sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw== + dependencies: + cross-spawn "^7.0.1" + cross-spawn@^6.0.0, cross-spawn@^6.0.5: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" @@ -4310,7 +4504,7 @@ cross-spawn@^6.0.0, cross-spawn@^6.0.5: shebang-command "^1.2.0" which "^1.2.9" -cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: +cross-spawn@^7.0.0, cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -4462,6 +4656,11 @@ dateformat@^3.0.0: resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q== +debounce@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.1.tgz#38881d8f4166a5c5848020c11827b834bcb3e0a5" + integrity sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug== + debug@2.6.9, debug@^2.2.0, debug@^2.3.3: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" @@ -4696,6 +4895,13 @@ destroy@1.2.0: resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== +detect-gpu@^5.0.28: + version "5.0.38" + resolved "https://registry.yarnpkg.com/detect-gpu/-/detect-gpu-5.0.38.tgz#1c05ce728ea1229d16db15b865631609bf0d6952" + integrity sha512-36QeGHSXYcJ/RfrnPEScR8GDprbXFG4ZhXsfVNVHztZr38+fRxgHnJl3CjYXXjbeRUhu3ZZBJh6Lg0A9v0Qd8A== + dependencies: + webgl-constants "^1.1.1" + detect-indent@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-5.0.0.tgz#3871cc0a6a002e8c3e5b3cf7f336264675f06b9d" @@ -4909,6 +5115,11 @@ dotenv@^16.0.0: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f" integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg== +draco3d@^1.4.1: + version "1.5.7" + resolved "https://registry.yarnpkg.com/draco3d/-/draco3d-1.5.7.tgz#94f9bce293eb8920c159dc91a4ce9124a9e899e0" + integrity sha512-m6WCKt/erDXcw+70IJXnG7M3awwQPAsZvJGX5zY7beBqpELw6RDGkYVU0W43AFxye4pDZ5i2Lbyc/NNGqwjUVQ== + duplexer@^0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" @@ -5716,6 +5927,16 @@ fb-watchman@^2.0.0: dependencies: bser "2.1.1" +fflate@^0.6.9: + version "0.6.10" + resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.6.10.tgz#5f40f9659205936a2d18abf88b2e7781662b6d43" + integrity sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg== + +fflate@~0.8.2: + version "0.8.2" + resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.8.2.tgz#fc8631f5347812ad6028bbe4a2308b2792aa1dea" + integrity sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A== + figgy-pudding@^3.4.1, figgy-pudding@^3.5.1: version "3.5.2" resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e" @@ -6287,6 +6508,11 @@ globby@^9.2.0: pify "^4.0.1" slash "^2.0.0" +glsl-noise@^0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/glsl-noise/-/glsl-noise-0.0.0.tgz#367745f3a33382c0eeec4cb54b7e99cfc1d7670b" + integrity sha512-b/ZCF6amfAUb7dJM/MxRs7AetQEahYzJ8PtgfrmEdtw6uyGOr+ZSGtgjFm6mfsBkxJ4d2W7kg+Nlqzqvn3Bc0w== + gopd@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" @@ -7414,6 +7640,13 @@ istanbul-reports@^3.1.3: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" +its-fine@^1.0.6: + version "1.2.5" + resolved "https://registry.yarnpkg.com/its-fine/-/its-fine-1.2.5.tgz#5466c287f86a0a73e772c8d8d515626c97195dc9" + integrity sha512-fXtDA0X0t0eBYAGLVM5YsgJGsJ5jEmqZEPrGbzdf5awjv0xE7nqv3TVnvtUF060Tkes15DbDAKW/I48vsb6SyA== + dependencies: + "@types/react-reconciler" "^0.28.0" + jackspeak@^2.3.6: version "2.3.6" resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.3.6.tgz#647ecc472238aee4b06ac0e461acc21a8c505ca8" @@ -8176,6 +8409,11 @@ lodash._reinterpolate@^3.0.0: resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" integrity sha512-xYHt68QRoYGjeeM/XOE1uJtvXQAgvszfBhjV4yvsQH0u2i9I6cI6c6/eG4Hh3UAOVn0y/xAXwmTzEay49Q//HA== +lodash.clamp@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/lodash.clamp/-/lodash.clamp-4.0.3.tgz#5c24bedeeeef0753560dc2b4cb4671f90a6ddfaa" + integrity sha512-HvzRFWjtcguTW7yd8NJBshuNaCa8aqNFtnswdT7f/cMd/1YKy5Zzoq4W/Oxvnx9l7aeY258uSdDfM793+eLsVg== + lodash.clonedeep@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" @@ -8216,6 +8454,16 @@ lodash.merge@^4.6.2: resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== +lodash.omit@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.omit/-/lodash.omit-4.5.0.tgz#6eb19ae5a1ee1dd9df0b969e66ce0b7fa30b5e60" + integrity sha512-XeqSp49hNGmlkj2EJlfrQFIzQ6lXdNro9sddtQzcJY8QaoC2GO0DT7xaIokHeyM+mIT0mPMlPvkYzg2xCuHdZg== + +lodash.pick@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3" + integrity sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q== + lodash.set@^4.3.2: version "4.3.2" resolved "https://registry.yarnpkg.com/lodash.set/-/lodash.set-4.3.2.tgz#d8757b1da807dde24816b0d6a84bea1a76230b23" @@ -8315,6 +8563,11 @@ lz-string@^1.5.0: resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.5.0.tgz#c1ab50f77887b712621201ba9fd4e3a6ed099941" integrity sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ== +maath@^0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/maath/-/maath-0.9.0.tgz#425a9600dfc5d0aecfa48029a2e3eea852531b06" + integrity sha512-aAR8hoUqPxlsU8VOxkS9y37jhUzdUxM017NpCuxFU1Gk+nMaZASZxymZrV8LRSHzRk/watlbfyNKu6XPUhCFrQ== + macos-release@^2.2.0: version "2.5.1" resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-2.5.1.tgz#bccac4a8f7b93163a8d163b8ebf385b3c5f55bf9" @@ -8514,6 +8767,16 @@ merge2@^1.2.3, merge2@^1.3.0, merge2@^1.4.1: resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== +meshline@^3.1.6: + version "3.3.1" + resolved "https://registry.yarnpkg.com/meshline/-/meshline-3.3.1.tgz#20decfd5cdd25c8469e862ddf0ab1ad167759734" + integrity sha512-/TQj+JdZkeSUOl5Mk2J7eLcYTLiQm2IDzmlSvYm7ov15anEcDJ92GHqqazxTSreeNgfnYu24kiEvvv0WlbCdFQ== + +meshoptimizer@~0.18.1: + version "0.18.1" + resolved "https://registry.yarnpkg.com/meshoptimizer/-/meshoptimizer-0.18.1.tgz#cdb90907f30a7b5b1190facd3b7ee6b7087797d8" + integrity sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw== + methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" @@ -9753,6 +10016,11 @@ possible-typed-array-names@^1.0.0: resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz#89bb63c6fada2c3e90adc4a647beeeb39cc7bf8f" integrity sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q== +potpack@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/potpack/-/potpack-1.0.2.tgz#23b99e64eb74f5741ffe7656b5b5c4ddce8dfc14" + integrity sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ== + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" @@ -9837,7 +10105,7 @@ promzard@^0.3.0: dependencies: read "1" -prop-types@^15.7.2: +prop-types@^15.6.0, prop-types@^15.7.2: version "15.8.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -10047,6 +10315,13 @@ raw-body@2.5.2: iconv-lite "0.4.24" unpipe "1.0.0" +react-composer@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/react-composer/-/react-composer-5.0.3.tgz#7beb9513da5e8687f4f434ea1333ef36a4f3091b" + integrity sha512-1uWd07EME6XZvMfapwZmc7NgCZqDemcvicRi3wMJzXsQLvZ3L7fTHVyPy1bZdnWXM4iPjYuNE+uJ41MLKeTtnA== + dependencies: + prop-types "^15.6.0" + react-dom@^18.3.1: version "18.3.1" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.3.1.tgz#c2265d79511b57d479b3dd3fdfa51536494c5cb4" @@ -10084,6 +10359,19 @@ react-is@^17.0.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== +react-merge-refs@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/react-merge-refs/-/react-merge-refs-1.1.0.tgz#73d88b892c6c68cbb7a66e0800faa374f4c38b06" + integrity sha512-alTKsjEL0dKH/ru1Iyn7vliS2QRcBp9zZPGoWxUOvRGWPUYgjo+V01is7p04It6KhgrzhJGnIj9GgX8W4bZoCQ== + +react-reconciler@^0.27.0: + version "0.27.0" + resolved "https://registry.yarnpkg.com/react-reconciler/-/react-reconciler-0.27.0.tgz#360124fdf2d76447c7491ee5f0e04503ed9acf5b" + integrity sha512-HmMDKciQjYmBRGuuhIaKA1ba/7a+UsM5FzOZsMO2JYHt9Jh8reCb7j1eDC95NOyUlKM9KRyvdx0flBuDvYSBoA== + dependencies: + loose-envify "^1.1.0" + scheduler "^0.21.0" + react-shallow-renderer@^16.15.0: version "16.15.0" resolved "https://registry.yarnpkg.com/react-shallow-renderer/-/react-shallow-renderer-16.15.0.tgz#48fb2cf9b23d23cde96708fe5273a7d3446f4457" @@ -10092,6 +10380,13 @@ react-shallow-renderer@^16.15.0: object-assign "^4.1.1" react-is "^16.12.0 || ^17.0.0 || ^18.0.0" +react-use-measure@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/react-use-measure/-/react-use-measure-2.1.1.tgz#5824537f4ee01c9469c45d5f7a8446177c6cc4ba" + integrity sha512-nocZhN26cproIiIduswYpV5y5lQpSQS1y/4KuvUCjSKmw7ZWIS/+g3aFnX3WdBkyuGUtTLif3UTqnLLhbDoQig== + dependencies: + debounce "^1.2.1" + react@^18.3.1: version "18.3.1" resolved "https://registry.yarnpkg.com/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891" @@ -10637,6 +10932,13 @@ saxes@^6.0.0: dependencies: xmlchars "^2.2.0" +scheduler@^0.21.0: + version "0.21.0" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.21.0.tgz#6fd2532ff5a6d877b6edb12f00d8ab7e8f308820" + integrity sha512-1r87x5fz9MXqswA2ERLo0EbOAU74DpIUO090gIasYTqlVoJeMcl+Z1Rg7WHz+qtPujhS/hGIt9kxZOYBV3faRQ== + dependencies: + loose-envify "^1.1.0" + scheduler@^0.23.2: version "0.23.2" resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.2.tgz#414ba64a3b282892e944cf2108ecc078d115cdc3" @@ -11154,6 +11456,16 @@ static-extend@^0.1.1: define-property "^0.2.5" object-copy "^0.1.0" +stats-gl@^1.0.4: + version "1.0.7" + resolved "https://registry.yarnpkg.com/stats-gl/-/stats-gl-1.0.7.tgz#b7fa2e0d464c8b7f4c57aa6a9bae33ed09762abe" + integrity sha512-vZI82CjefSxLC1bjw36z28v0+QE9rJKymGlXtfWu+ipW70ZEAwa4EbO4LxluAfLfpqiaAS04NzpYBRLDeAwYWQ== + +stats.js@^0.17.0: + version "0.17.0" + resolved "https://registry.yarnpkg.com/stats.js/-/stats.js-0.17.0.tgz#b1c3dc46d94498b578b7fd3985b81ace7131cc7d" + integrity sha512-hNKz8phvYLPEcRkeG1rsGmV5ChMjKDAWU7/OJJdDErPBNChQXxCo3WZurGpnWc6gZhAzEPFad1aVgyOANH1sMw== + statuses@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" @@ -11453,6 +11765,11 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== +suspend-react@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/suspend-react/-/suspend-react-0.1.3.tgz#a52f49d21cfae9a2fb70bd0c68413d3f9d90768e" + integrity sha512-aqldKgX9aZqpoDp3e8/BZ8Dm7x1pJl+qI3ZKxDN0i/IQTWUwBx/ManmlVJ3wowqbno6c2bmiIfs+Um6LbsjJyQ== + svg-to-pdfkit@^0.1.8: version "0.1.8" resolved "https://registry.yarnpkg.com/svg-to-pdfkit/-/svg-to-pdfkit-0.1.8.tgz#5921765922044843f0c1a5b25ec1ef8a4a33b8af" @@ -11582,6 +11899,28 @@ thingies@^1.20.0: resolved "https://registry.yarnpkg.com/thingies/-/thingies-1.21.0.tgz#e80fbe58fd6fdaaab8fad9b67bd0a5c943c445c1" integrity sha512-hsqsJsFMsV+aD4s3CWKk85ep/3I9XzYV/IXaSouJMYIoDlgyi11cBhsqYe9/geRfB0YIikBQg6raRaM+nIMP9g== +three-mesh-bvh@^0.6.7: + version "0.6.8" + resolved "https://registry.yarnpkg.com/three-mesh-bvh/-/three-mesh-bvh-0.6.8.tgz#f27d18ca75bdc59316dff0f561af8fb316621a54" + integrity sha512-EGebF9DZx1S8+7OZYNNTT80GXJZVf+UYXD/HyTg/e2kR/ApofIFfUS4ZzIHNnUVIadpnLSzM4n96wX+l7GMbnQ== + +three-stdlib@^2.28.0: + version "2.30.3" + resolved "https://registry.yarnpkg.com/three-stdlib/-/three-stdlib-2.30.3.tgz#134562f52e6661d10bb2b9409c3d627b25a00481" + integrity sha512-rYr8PqMljMza+Ct8kQk90Y7y+YcWoPu1thfYv5YGCp0hytNRbxSQWXY4GpdTGymCj3bDggEBpxso53C3pPwhIw== + dependencies: + "@types/draco3d" "^1.4.0" + "@types/offscreencanvas" "^2019.6.4" + "@types/webxr" "^0.5.2" + draco3d "^1.4.1" + fflate "^0.6.9" + potpack "^1.0.1" + +three@^0.164.1: + version "0.164.1" + resolved "https://registry.yarnpkg.com/three/-/three-0.164.1.tgz#b742f76bd8dfd3736ba0d86a12dfddb73c5cdcc0" + integrity sha512-iC/hUBbl1vzFny7f5GtqzVXYjMJKaTPxiCxXfrvVdBi1Sf+jhd1CAkitiFwC7mIBFCo3MrDLJG97yisoaWig0w== + through2@^2.0.0, through2@^2.0.2: version "2.0.5" resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" @@ -11753,6 +12092,26 @@ trim@0.0.1: resolved "https://registry.yarnpkg.com/trim/-/trim-0.0.1.tgz#5858547f6b290757ee95cccc666fb50084c460dd" integrity sha512-YzQV+TZg4AxpKxaTHK3c3D+kRDCGVEE7LemdlQZoQXn0iennk10RsIoY6ikzAqJTc9Xjl9C1/waHom/J86ziAQ== +troika-three-text@^0.47.2: + version "0.47.2" + resolved "https://registry.yarnpkg.com/troika-three-text/-/troika-three-text-0.47.2.tgz#fdf89059c010563bb829262b20c41f69ca79b712" + integrity sha512-qylT0F+U7xGs+/PEf3ujBdJMYWbn0Qci0kLqI5BJG2kW1wdg4T1XSxneypnF05DxFqJhEzuaOR9S2SjiyknMng== + dependencies: + bidi-js "^1.0.2" + troika-three-utils "^0.47.2" + troika-worker-utils "^0.47.2" + webgl-sdf-generator "1.1.1" + +troika-three-utils@^0.47.2: + version "0.47.2" + resolved "https://registry.yarnpkg.com/troika-three-utils/-/troika-three-utils-0.47.2.tgz#af49ca694245dce631963d5fefe4e8e1b8af9044" + integrity sha512-/28plhCxfKtH7MSxEGx8e3b/OXU5A0xlwl+Sbdp0H8FXUHKZDoksduEKmjQayXYtxAyuUiCRunYIv/8Vi7aiyg== + +troika-worker-utils@^0.47.2: + version "0.47.2" + resolved "https://registry.yarnpkg.com/troika-worker-utils/-/troika-worker-utils-0.47.2.tgz#e7c5de5f37d56c072b13fa8112bb844e048ff46c" + integrity sha512-mzss4MeyzUkYBppn4x5cdAqrhBHFEuVmMMgLMTyFV23x6GvQMyo+/R5E5Lsbrt7WSt5RfvewjcwD1DChRTA9lA== + trough@^1.0.0: version "1.0.5" resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.5.tgz#b8b639cefad7d0bb2abd37d433ff8293efa5f406" @@ -12213,6 +12572,11 @@ utila@~0.4: resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c" integrity sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA== +utility-types@^3.10.0: + version "3.11.0" + resolved "https://registry.yarnpkg.com/utility-types/-/utility-types-3.11.0.tgz#607c40edb4f258915e901ea7995607fdf319424c" + integrity sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw== + utils-merge@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" @@ -12228,7 +12592,7 @@ uuid@^8.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== -uuid@^9.0.0: +uuid@^9.0.0, uuid@^9.0.1: version "9.0.1" resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30" integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== @@ -12334,6 +12698,16 @@ wcwidth@^1.0.0: dependencies: defaults "^1.0.3" +webgl-constants@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/webgl-constants/-/webgl-constants-1.1.1.tgz#f9633ee87fea56647a60b9ce735cbdfb891c6855" + integrity sha512-LkBXKjU5r9vAW7Gcu3T5u+5cvSvh5WwINdr0C+9jpzVB41cjQAP5ePArDtk/WHYdVj0GefCgM73BA7FlIiNtdg== + +webgl-sdf-generator@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/webgl-sdf-generator/-/webgl-sdf-generator-1.1.1.tgz#3e1b422b3d87cd3cc77f2602c9db63bc0f6accbd" + integrity sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA== + webidl-conversions@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" @@ -12796,3 +13170,8 @@ yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + +zustand@^3.5.13, zustand@^3.7.1: + version "3.7.2" + resolved "https://registry.yarnpkg.com/zustand/-/zustand-3.7.2.tgz#7b44c4f4a5bfd7a8296a3957b13e1c346f42514d" + integrity sha512-PIJDIZKtokhof+9+60cpockVOq05sJzHCriyvaLBmEJixseQ1a5Kdov6fWZfWOu5SK9c+FhH1jU0tntLxRJYMA==