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:
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
const { title } = defineProps<{ title: string }>();
|
||||
defineProps<{ title: string }>();
|
||||
|
||||
const showForm = ref<boolean>(false);
|
||||
</script>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user