1
0
mirror of https://github.com/robonen/canvas-3d.git synced 2026-03-20 02:44:40 +00:00

refactor(app): update deps, decomposition, formatting

This commit is contained in:
2024-05-26 03:26:35 +07:00
parent 314382e7ba
commit 0e2a71b7a8
37 changed files with 10865 additions and 14254 deletions

View File

@@ -1 +0,0 @@

View File

@@ -7,7 +7,9 @@
@font-face {
font-family: $font-family;
src: url('#{$font-with-path}.eot');
src: local('#{$font-with-dash}'), local('#{$font-with-space}'),
src:
local('#{$font-with-dash}'),
local('#{$font-with-space}'),
url('#{$font-with-path}.eot?#iefix') format('embedded-opentype'),
url('#{$font-with-path}.woff2') format('woff2'),
url('#{$font-with-path}.woff') format('woff'),
@@ -19,7 +21,8 @@
@font-face {
font-family: $font-family;
src: url('#{$font-with-path}Italic.eot');
src: local('#{$font-with-dash}Italic'),
src:
local('#{$font-with-dash}Italic'),
local('#{$font-with-space} Italic'),
url('#{$font-with-path}Italic.eot?#iefix') format('embedded-opentype'),
url('#{$font-with-path}Italic.woff2') format('woff2'),
@@ -39,15 +42,16 @@ $fonts: (
Black: 700,
);
@include MakeFont('Formular', $fonts, '@/assets/fonts/formular');
@font-face {
font-family: 'Computer Modern Serif';
src: url('@/assets/fonts/computer-modern/cmunrm.eot');
src: url('@/assets/fonts/computer-modern/cmunrm.eot?#iefix')
src:
url('@/assets/fonts/computer-modern/cmunrm.eot?#iefix')
format('embedded-opentype'),
url('@/assets/fonts/computer-modern/cmunrm.woff') format('woff'),
url('@/assets/fonts/computer-modern/cmunrm.ttf') format('truetype');
font-weight: normal;
font-style: normal;
}
@include MakeFont('Formular', $fonts, '@/assets/fonts/formular');

View File

@@ -1,5 +1,6 @@
<script setup lang="ts">
const { title } = defineProps<{ title: string }>();
defineProps<{ title: string }>();
const showForm = ref<boolean>(false);
</script>

View File

@@ -1,321 +1,7 @@
<script setup lang="ts">
const canvas = ref<HTMLCanvasElement | null>(null);
onMounted(() => {
if (!canvas.value) return;
canvas.value.width = window.innerWidth;
canvas.value.height = window.innerHeight;
const ctx = canvas.value.getContext('2d');
if (!ctx) return;
ctx.fillStyle = 'red';
type Point = [number, number, number, number];
const sizeX = canvas.value.width;
const sizeY = canvas.value.height;
const centerX = sizeX / 2;
const centerY = sizeY / 2;
const figureSize = 200;
const figureList = {
[Figures.CUBE]: {
points: [
[0, 0, 0, 1],
[0, 0, 200, 1],
[0, 200, 0, 1],
[0, 200, 200, 1],
[200, 0, 0, 1],
[200, 0, 200, 1],
[200, 200, 0, 1],
[200, 200, 200, 1],
] as Point[],
faces: [
[0, 1, 3, 2],
[0, 1, 5, 4],
[0, 2, 6, 4],
[1, 3, 7, 5],
[2, 3, 7, 6],
[4, 5, 7, 6],
],
},
[Figures.OCTAHEDRON]: {
points: [
[0, 0, 100, 1],
[100, 100, 0, 1],
[100, -100, 0, 1],
[-100, -100, 0, 1],
[-100, 100, 0, 1],
[0, 0, -100, 1],
] as Point[],
faces: [
[0, 1, 2],
[0, 2, 3],
[0, 3, 4],
[0, 4, 1],
[5, 1, 2],
[5, 2, 3],
[5, 3, 4],
[5, 4, 1],
],
},
[Figures.TRIHEDRAL_PYRAMID]: {
points: [
[0, 0, 100, 1],
[0, 80, 0, 1],
[86.6, -50, 0, 1],
[-86.6, -50, 0, 1],
] as Point[],
faces: [
[0, 1, 2],
[0, 2, 3],
[0, 3, 1],
[1, 2, 3],
],
},
[Figures.SQUARE_PYRAMID]: {
points: [
[0, 0, figureSize, 1],
[100, 100, 0, 1],
[100, -100, 0, 1],
[-100, -100, 0, 1],
[-100, 100, 0, 1],
] as Point[],
faces: [
[0, 1, 2],
[0, 2, 3],
[0, 3, 4],
[0, 4, 1],
[1, 2, 3, 4],
],
},
[Figures.PENTAGONAL_PYRAMID]: {
points: [
[0, 0, figureSize, 1],
[0, 100, 0, 1],
[95.1, 30.9, 0, 1],
[58.8, -80.9, 0, 1],
[-58.8, -80.9, 0, 1],
[-95.1, 30.9, 0, 1],
] as Point[],
faces: [
[0, 1, 2],
[0, 2, 3],
[0, 3, 4],
[0, 4, 5],
[0, 5, 1],
[1, 2, 3, 4, 5],
],
},
};
const identityMatrix: Point[] = [
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1],
];
const translationMatrix: Point[] = [
[1, 0, 0, 0],
[0, -1, 0, 0],
[0, 0, 1, 0],
[centerX, centerY, 0, 1],
];
// Rotate around X axis
const rotateX = (angle: number): Point[] => {
const rad = (angle * Math.PI) / 180;
const cos = Math.cos(rad);
const sin = Math.sin(rad);
return [
[1, 0, 0, 0],
[0, cos, -sin, 0],
[0, sin, cos, 0],
[0, 0, 0, 1],
];
};
// Rotate around Y axis
const rotateY = (angle: number): Point[] => {
const rad = (angle * Math.PI) / 180;
const cos = Math.cos(rad);
const sin = Math.sin(rad);
return [
[cos, 0, sin, 0],
[0, 1, 0, 0],
[-sin, 0, cos, 0],
[0, 0, 0, 1],
];
};
// Rotate around Z axis
const rotateZ = (angle: number): Point[] => {
const rad = (angle * Math.PI) / 180;
const cos = Math.cos(rad);
const sin = Math.sin(rad);
return [
[cos, -sin, 0, 0],
[sin, cos, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1],
];
};
const rotate = (x: number, y: number, z: number): Point[] => {
return mul([rotateX(x), rotateY(y), rotateZ(z)]);
};
// Translate
const translate = (x: number, y: number, z: number): Point[] => {
return [
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0],
[x, y, z, 1],
];
};
// Scale
const scaleMatrix = (x: number, y: number, z: number): Point[] => {
return [
[x, 0, 0, 0],
[0, y, 0, 0],
[0, 0, z, 0],
[0, 0, 0, 1],
];
};
const projections = {
[Projections.NONE]: (): Point[] => identityMatrix,
[Projections.ISOMETRIC]: (): Point[] => [
[0.707, -0.408, 0, 0],
[0, 0.816, 0, 0],
[-0.707, -0.408, 1, 0],
[0, 0, 0, 1],
],
[Projections.DIMETRIC]: (): Point[] => [
[0.926, 0.134, 0, 0],
[0, 0.935, 0, 0],
[0.378, -0.327, 0, 0],
[0, 0, 0, 1],
],
[Projections.TRIMETRIC]: (): Point[] => [
[0.866, 0.354, 0, 0],
[0, 0.707, 0, 0],
[0.5, -0.612, 0, 0],
[0, 0, 0, 1],
],
[Projections.ONE_POINT_PERSPECTIVE]: (): Point[] => [
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 0, 0.001],
[0, 0, 0, 1],
],
[Projections.TWO_POINT_PERSPECTIVE]: (): Point[] => [
[1, 0, 0, 0.001],
[0, 1, 0, 0.001],
[0, 0, 0, 0],
[0, 0, 0, 1],
],
};
// Multiply array of points by matrix
const multiply = (points: Point[], matrix: Point[]): Point[] => {
const result: Point[] = [];
for (const point of points) {
const [x, y, z, w] = point;
const [x1, y1, z1, w1] = matrix[0];
const [x2, y2, z2, w2] = matrix[1];
const [x3, y3, z3, w3] = matrix[2];
const [x4, y4, z4, w4] = matrix[3];
result.push([
x * x1 + y * x2 + z * x3 + w * x4,
x * y1 + y * y2 + z * y3 + w * y4,
x * z1 + y * z2 + z * z3 + w * z4,
x * w1 + y * w2 + z * w3 + w * w4,
]);
}
return result;
};
const mul = (matrices: Point[][]) => {
let result = matrices[0];
for (let i = 1; i < matrices.length; i++) {
result = multiply(result, matrices[i]);
}
return result;
};
// Draw figure
const drawFigure = (points: Point[], faces: number[][]) => {
ctx.clearRect(0, 0, sizeX, sizeY);
for (const face of faces) {
ctx.beginPath();
for (let i = 0; i < face.length; i++) {
const [x, y] = points[face[i]];
if (i === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
}
ctx.closePath();
ctx.stroke();
}
};
const { currentFigure } = useFigure();
useTransformations((translation, rotation, scale, prj) => {
const matrix = mul([
figureList[currentFigure.value].points,
scaleMatrix(scale[0], scale[1], scale[2]),
rotate(rotation[0], rotation[1], rotation[2]),
translate(translation[0], translation[1], translation[2]),
projections[prj](),
]);
for (let i = 0; i < matrix.length; i++) {
matrix[i][0] = matrix[i][0] / matrix[i][3];
matrix[i][1] = matrix[i][1] / matrix[i][3];
matrix[i][2] = matrix[i][2] / matrix[i][3];
matrix[i][3] = 1;
}
drawFigure(
mul([matrix, translationMatrix]),
figureList[currentFigure.value].faces
);
});
});
useCanvas(canvas);
</script>
<template>

View File

@@ -1,36 +1,31 @@
<script setup lang="ts">
import { HTMLElementEvent } from '@/types/dom';
const {
label,
min,
max,
currentValue,
defaultValue,
step = 0.1,
} = defineProps<{
label: string;
min: number;
max: number;
currentValue: number;
defaultValue: number;
step?: number;
}>();
const props = withDefaults(
defineProps<{
label: string;
min: number;
max: number;
currentValue: number;
defaultValue: number;
step?: number;
}>(),
{
step: 0.1,
}
);
const emit = defineEmits<{
(event: 'change', value: number): void;
}>();
const value = ref<number>(currentValue);
const value = ref<number>(props.currentValue);
const onChange = (event: Event) => {
const { target } = event as HTMLElementEvent<HTMLInputElement>;
value.value = target.valueAsNumber;
value.value = (event.target as HTMLInputElement).valueAsNumber;
emit('change', value.value);
};
const reset = () => {
value.value = defaultValue;
value.value = props.defaultValue;
emit('change', value.value);
};
</script>
@@ -86,12 +81,12 @@ const reset = () => {
&::-webkit-outer-spin-button,
&::-webkit-inner-spin-button {
-webkit-appearance: none;
appearance: none;
margin: 0;
}
&[type='number'] {
-moz-appearance: textfield;
appearance: textfield;
}
}
@@ -108,7 +103,7 @@ const reset = () => {
}
.range__input {
-webkit-appearance: none;
appearance: none;
flex: 1;
height: 6px;
border-radius: 4px;
@@ -122,7 +117,6 @@ const reset = () => {
}
&::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 14px;
height: 14px;

View File

@@ -1,8 +1,13 @@
<script setup lang="ts">
const { title, isActive = false } = defineProps<{
title?: string;
isActive?: boolean;
}>();
withDefaults(
defineProps<{
title?: string;
isActive?: boolean;
}>(),
{
isActive: false,
}
);
</script>
<template>
@@ -30,7 +35,9 @@ const { title, isActive = false } = defineProps<{
color: #67122c;
padding: 16px;
font-size: 17px;
transition: background-color 0.2s, transform 0.2s;
transition:
background-color 0.2s,
transform 0.2s;
&_active {
background-color: #fdd2e2;

View File

@@ -1,6 +1,16 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M4.5 15.75l7.5-7.5 7.5 7.5"/>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-6 h-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M4.5 15.75l7.5-7.5 7.5 7.5"
/>
</svg>
</template>

View File

@@ -1,6 +1,16 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M18.75 19.5l-7.5-7.5 7.5-7.5m-6 15L5.25 12l7.5-7.5"/>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-6 h-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M18.75 19.5l-7.5-7.5 7.5-7.5m-6 15L5.25 12l7.5-7.5"
/>
</svg>
</template>

View File

@@ -1,7 +1,16 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5"/>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-6 h-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5"
/>
</svg>
</template>

View File

@@ -1,6 +1,16 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5"/>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-6 h-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M19.5 8.25l-7.5 7.5-7.5-7.5"
/>
</svg>
</template>

View File

@@ -0,0 +1,44 @@
const updateCanvas = (
canvas: HTMLCanvasElement,
width: number,
height: number
) => {
const centerX = width / 2;
const centerY = height / 2;
const { ctx } = initCanvas(canvas, width, height);
const { currentFigure } = useFigure();
useTransformations((translation, rotation, scale, prj) => {
const matrix = mul([
figuresMap[currentFigure.value].points,
scaleMatrix(scale[0], scale[1], scale[2]),
rotate(rotation[0], rotation[1], rotation[2]),
translate(translation[0], translation[1], translation[2]),
projectionsMap[prj](),
]);
for (let i = 0; i < matrix.length; i++) {
matrix[i][0] = matrix[i][0] / matrix[i][3];
matrix[i][1] = matrix[i][1] / matrix[i][3];
matrix[i][2] = matrix[i][2] / matrix[i][3];
matrix[i][3] = 1;
}
drawFigure(
ctx,
mul([matrix, translationMatrix(centerX, centerY)]),
figuresMap[currentFigure.value].faces
);
});
};
export const useCanvas = (canvas: Ref<HTMLCanvasElement | null>) => {
const { width, height } = useResizeObserver(document.body);
watch([width, height], ([width, height]) => {
if (!canvas.value) return;
updateCanvas(canvas.value, width, height);
});
};

View File

@@ -1,3 +1,4 @@
// Types
export const enum Figures {
CUBE,
OCTAHEDRON,
@@ -6,6 +7,7 @@ export const enum Figures {
PENTAGONAL_PYRAMID,
}
// Composable
const currentFigure = ref<Figures>(Figures.PENTAGONAL_PYRAMID);
export const useFigure = () => {

View File

@@ -0,0 +1,41 @@
const debounce = (delay: number, fn: Function) => {
let timer: undefined | ReturnType<typeof setTimeout>;
return (...args: any[]) => {
clearTimeout(timer);
timer = setTimeout(() => {
fn(...args);
}, delay);
};
};
export const useResizeObserver = (
element: Ref<HTMLElement | null> | HTMLElement
) => {
const width = ref(0);
const height = ref(0);
onMounted(() => {
const taget = toValue(element);
if (!taget) return;
const updateSize = debounce(100, (entries: ResizeObserverEntry[]) => {
const { width: newWidth, height: newHeight } = entries[0].contentRect;
width.value = newWidth;
height.value = newHeight;
});
const observer = new ResizeObserver(updateSize);
observer.observe(taget);
onBeforeUnmount(() => {
observer.disconnect();
});
});
return { width, height };
};

View File

@@ -1,3 +1,4 @@
// Types
export const enum Projections {
NONE,
ISOMETRIC,
@@ -18,6 +19,7 @@ type fn =
) => void)
| null;
// Composable
const translation = reactive<XYZ>([0, 0, 0]);
const rotation = reactive<XYZ>([0, 0, 0]);
const scale = reactive<XYZ>([1, 1, 1]);

102
src/const/figures.ts Normal file
View File

@@ -0,0 +1,102 @@
export const figuresMenu = [
{ figure: Figures.CUBE, title: 'Куб' },
{ figure: Figures.OCTAHEDRON, title: 'Октаэдр' },
{ figure: Figures.TRIHEDRAL_PYRAMID, title: 'Трехгранная пирамида' },
{ figure: Figures.SQUARE_PYRAMID, title: 'Четырехгранная пирамида' },
{ figure: Figures.PENTAGONAL_PYRAMID, title: 'Пятигранная пирамиида' },
];
export const figuresMap = {
[Figures.CUBE]: {
points: [
[0, 0, 0, 1],
[0, 0, 200, 1],
[0, 200, 0, 1],
[0, 200, 200, 1],
[200, 0, 0, 1],
[200, 0, 200, 1],
[200, 200, 0, 1],
[200, 200, 200, 1],
] as Point[],
faces: [
[0, 1, 3, 2],
[0, 1, 5, 4],
[0, 2, 6, 4],
[1, 3, 7, 5],
[2, 3, 7, 6],
[4, 5, 7, 6],
],
},
[Figures.OCTAHEDRON]: {
points: [
[0, 0, 100, 1],
[100, 100, 0, 1],
[100, -100, 0, 1],
[-100, -100, 0, 1],
[-100, 100, 0, 1],
[0, 0, -100, 1],
] as Point[],
faces: [
[0, 1, 2],
[0, 2, 3],
[0, 3, 4],
[0, 4, 1],
[5, 1, 2],
[5, 2, 3],
[5, 3, 4],
[5, 4, 1],
],
},
[Figures.TRIHEDRAL_PYRAMID]: {
points: [
[0, 0, 100, 1],
[0, 80, 0, 1],
[86.6, -50, 0, 1],
[-86.6, -50, 0, 1],
] as Point[],
faces: [
[0, 1, 2],
[0, 2, 3],
[0, 3, 1],
[1, 2, 3],
],
},
[Figures.SQUARE_PYRAMID]: {
points: [
[0, 0, 200, 1],
[100, 100, 0, 1],
[100, -100, 0, 1],
[-100, -100, 0, 1],
[-100, 100, 0, 1],
] as Point[],
faces: [
[0, 1, 2],
[0, 2, 3],
[0, 3, 4],
[0, 4, 1],
[1, 2, 3, 4],
],
},
[Figures.PENTAGONAL_PYRAMID]: {
points: [
[0, 0, 200, 1],
[0, 100, 0, 1],
[95.1, 30.9, 0, 1],
[58.8, -80.9, 0, 1],
[-58.8, -80.9, 0, 1],
[-95.1, 30.9, 0, 1],
] as Point[],
faces: [
[0, 1, 2],
[0, 2, 3],
[0, 3, 4],
[0, 4, 5],
[0, 5, 1],
[1, 2, 3, 4, 5],
],
},
};

View File

@@ -0,0 +1,55 @@
export const axes = ['x', 'y', 'z'];
export const projectionsMenu = [
{ projection: Projections.NONE, title: 'Без проекции' },
{ projection: Projections.ISOMETRIC, title: 'Изометрическая' },
{ projection: Projections.DIMETRIC, title: 'Диметрическая' },
{ projection: Projections.TRIMETRIC, title: 'Триметрическая' },
{
projection: Projections.ONE_POINT_PERSPECTIVE,
title: 'Одноточечная перспектива',
},
{
projection: Projections.TWO_POINT_PERSPECTIVE,
title: 'Двухточечная перспектива',
},
];
export const projectionsMap = {
[Projections.NONE]: (): Point[] => identityMatrix,
[Projections.ISOMETRIC]: (): Point[] => [
[0.707, -0.408, 0, 0],
[0, 0.816, 0, 0],
[-0.707, -0.408, 1, 0],
[0, 0, 0, 1],
],
[Projections.DIMETRIC]: (): Point[] => [
[0.926, 0.134, 0, 0],
[0, 0.935, 0, 0],
[0.378, -0.327, 0, 0],
[0, 0, 0, 1],
],
[Projections.TRIMETRIC]: (): Point[] => [
[0.866, 0.354, 0, 0],
[0, 0.707, 0, 0],
[0.5, -0.612, 0, 0],
[0, 0, 0, 1],
],
[Projections.ONE_POINT_PERSPECTIVE]: (): Point[] => [
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 0, 0.001],
[0, 0, 0, 1],
],
[Projections.TWO_POINT_PERSPECTIVE]: (): Point[] => [
[1, 0, 0, 0.001],
[0, 1, 0, 0.001],
[0, 0, 0, 0],
[0, 0, 0, 1],
],
};

View File

@@ -1,20 +1,4 @@
<script setup lang="ts">
const axes = ['x', 'y', 'z'];
const projections = [
{ projection: Projections.NONE, title: 'Без проекции' },
{ projection: Projections.ISOMETRIC, title: 'Изометрическая' },
{ projection: Projections.DIMETRIC, title: 'Диметрическая' },
{ projection: Projections.TRIMETRIC, title: 'Триметрическая' },
{
projection: Projections.ONE_POINT_PERSPECTIVE,
title: 'Одноточечная перспектива',
},
{
projection: Projections.TWO_POINT_PERSPECTIVE,
title: 'Двухточечная перспектива',
},
];
const activeProjection = ref<number>(0);
const transformations = useTransformations();
@@ -23,7 +7,7 @@ onMounted(() => (activeProjection.value = transformations.projection.value));
const setActiveProjection = (index: number) => {
activeProjection.value = index;
transformations.setProjection(projections[index].projection);
transformations.setProjection(projectionsMenu[index].projection);
};
</script>
@@ -69,7 +53,7 @@ const setActiveProjection = (index: number) => {
<Accordion title="Проекции">
<GridContainer>
<GridElement
v-for="(projection, i) in projections"
v-for="(projection, i) in projectionsMenu"
:key="projection.title"
:is-active="activeProjection === i"
:title="projection.title"

View File

@@ -1,22 +1,14 @@
<script setup lang="ts">
const allFigures = [
{ figure: Figures.CUBE, title: 'Куб' },
{ figure: Figures.OCTAHEDRON, title: 'Октаэдр' },
{ figure: Figures.TRIHEDRAL_PYRAMID, title: 'Трехгранная пирамида' },
{ figure: Figures.SQUARE_PYRAMID, title: 'Четырехгранная пирамида' },
{ figure: Figures.PENTAGONAL_PYRAMID, title: 'Пятигранная пирамиида' },
];
const activeFigure = ref<number>(0);
const figure = useFigure();
onMounted(() => (activeFigure.value = figure.currentFigure.value));
const selectFigure = (index: number) => {
const selectFigure = async (index: number) => {
activeFigure.value = index;
figure.setFigure(allFigures[index].figure);
navigateTo('/figure');
figure.setFigure(figuresMenu[index].figure);
await navigateTo('/figure');
};
</script>
@@ -24,7 +16,7 @@ const selectFigure = (index: number) => {
<h1>Выберете фигуру</h1>
<GridContainer>
<GridElement
v-for="(figure, i) in allFigures"
v-for="(figure, i) in figuresMenu"
:key="figure.title"
:title="figure.title"
:is-active="activeFigure === i"

View File

@@ -1,8 +1 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="256px" height="172px" viewBox="0 0 256 172" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
<title>NuxtJS</title>
<g>
<path d="M112.973416,9.2496789 C105.800602,-3.0832263 87.8689954,-3.0832263 80.6961815,9.2496789 L2.52446046,143.659628 C-4.64826776,155.992961 4.3176211,171.408985 18.6631204,171.408985 L79.688321,171.408985 C73.5584906,166.051862 71.2883417,156.784087 75.9271555,148.832569 L135.130926,47.3479175 L112.973416,9.2496789 Z" fill="#80EEC0"></path>
<path d="M162.504638,38.7329166 C168.440863,28.6423189 183.280784,28.6423189 189.217009,38.7329166 L253.910685,148.704498 C259.84691,158.795096 252.42695,171.408557 240.554499,171.408557 L111.167148,171.408557 C99.295126,171.408557 91.8747374,158.795096 97.8109626,148.704498 L162.504638,38.7329166 Z" fill="#00DC82"></path>
</g>
</svg>
<svg height="256" width="256" xmlns="http://www.w3.org/2000/svg" viewBox="-5.8 -5.8 69.6 69.6" xml:space="preserve" stroke="#000" stroke-width=".58"><path style="fill:#67122c" d="M29 58 3 45V13l26 13z"/><path style="fill:#fae9ef" d="m29 58 26-13V13L29 26z"/><path style="fill:#fdd2e2" d="M3 13 28 0l27 13-26 13z"/></svg>

Before

Width:  |  Height:  |  Size: 947 B

After

Width:  |  Height:  |  Size: 320 B

View File

@@ -1,5 +0,0 @@
export default defineEventHandler(() => {
return {
api: 'works',
};
});

View File

@@ -1,7 +0,0 @@
import http from 'k6/http';
import {sleep} from 'k6';
export default function () {
http.get('http://localhost');
sleep(1);
}

View File

@@ -1,3 +0,0 @@
export type HTMLElementEvent<T extends HTMLElement> = Event & {
target: T;
}

60
src/utils/canvas.ts Normal file
View File

@@ -0,0 +1,60 @@
export function initCanvas(
canvas: HTMLCanvasElement,
width: number,
height: number,
_dpi?: number
) {
const ctx = canvas.getContext('2d')!;
const dpr = window.devicePixelRatio || 1;
const bsr =
// @ts-expect-error vendor prefix
ctx.webkitBackingStorePixelRatio ||
// @ts-expect-error vendor prefix
ctx.mozBackingStorePixelRatio ||
// @ts-expect-error vendor prefix
ctx.msBackingStorePixelRatio ||
// @ts-expect-error vendor prefix
ctx.oBackingStorePixelRatio ||
// @ts-expect-error vendor prefix
ctx.backingStorePixelRatio ||
1;
const dpi = _dpi || dpr / bsr;
canvas.style.width = `${width}px`;
canvas.style.height = `${height}px`;
canvas.width = dpi * width;
canvas.height = dpi * height;
ctx.scale(dpi, dpi);
return { ctx, dpi };
}
export function drawFigure(
ctx: CanvasRenderingContext2D,
points: Point[],
faces: number[][]
) {
requestAnimationFrame(() => {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
for (const face of faces) {
ctx.beginPath();
for (let i = 0; i < face.length; i++) {
const [x, y] = points[face[i]];
if (i === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
}
ctx.closePath();
ctx.stroke();
}
});
}

114
src/utils/transform3d.ts Normal file
View File

@@ -0,0 +1,114 @@
export type Point = [number, number, number, number];
export const identityMatrix: Point[] = [
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1],
];
export const translationMatrix = (
centerX: number,
centerY: number
): Point[] => [
[1, 0, 0, 0],
[0, -1, 0, 0],
[0, 0, 1, 0],
[centerX, centerY, 0, 1],
];
export const rotateX = (angle: number): Point[] => {
const rad = (angle * Math.PI) / 180;
const cos = Math.cos(rad);
const sin = Math.sin(rad);
return [
[1, 0, 0, 0],
[0, cos, -sin, 0],
[0, sin, cos, 0],
[0, 0, 0, 1],
];
};
export const rotateY = (angle: number): Point[] => {
const rad = (angle * Math.PI) / 180;
const cos = Math.cos(rad);
const sin = Math.sin(rad);
return [
[cos, 0, sin, 0],
[0, 1, 0, 0],
[-sin, 0, cos, 0],
[0, 0, 0, 1],
];
};
export const rotateZ = (angle: number): Point[] => {
const rad = (angle * Math.PI) / 180;
const cos = Math.cos(rad);
const sin = Math.sin(rad);
return [
[cos, -sin, 0, 0],
[sin, cos, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1],
];
};
export const rotate = (x: number, y: number, z: number): Point[] => {
return mul([rotateX(x), rotateY(y), rotateZ(z)]);
};
// Translate
export const translate = (x: number, y: number, z: number): Point[] => {
return [
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0],
[x, y, z, 1],
];
};
// Scale
export const scaleMatrix = (x: number, y: number, z: number): Point[] => {
return [
[x, 0, 0, 0],
[0, y, 0, 0],
[0, 0, z, 0],
[0, 0, 0, 1],
];
};
// Multiply array of points by matrix
const multiply = (points: Point[], matrix: Point[]): Point[] => {
const result: Point[] = [];
for (const point of points) {
const [x, y, z, w] = point;
const [x1, y1, z1, w1] = matrix[0];
const [x2, y2, z2, w2] = matrix[1];
const [x3, y3, z3, w3] = matrix[2];
const [x4, y4, z4, w4] = matrix[3];
result.push([
x * x1 + y * x2 + z * x3 + w * x4,
x * y1 + y * y2 + z * y3 + w * y4,
x * z1 + y * z2 + z * z3 + w * z4,
x * w1 + y * w2 + z * w3 + w * w4,
]);
}
return result;
};
export const mul = (matrices: Point[][]) => {
let result = matrices[0];
for (let i = 1; i < matrices.length; i++) {
result = multiply(result, matrices[i]);
}
return result;
};