mirror of
https://github.com/robonen/canvas-3d.git
synced 2026-03-20 02:44:40 +00:00
feat(engine): all figures and transformations ready
This commit is contained in:
@@ -21,47 +21,116 @@ onMounted(() => {
|
|||||||
const centerX = sizeX / 2;
|
const centerX = sizeX / 2;
|
||||||
const centerY = sizeY / 2;
|
const centerY = sizeY / 2;
|
||||||
|
|
||||||
const figureSizeH = 200;
|
const figureSize = 200;
|
||||||
const figureSizeW = 200;
|
|
||||||
|
|
||||||
const points: Point[] = [
|
const figureList = {
|
||||||
[0, 0, figureSizeH, 1],
|
[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],
|
[0, 100, 0, 1],
|
||||||
[95.1, 30.9, 0, 1],
|
[95.1, 30.9, 0, 1],
|
||||||
[58.8, -80.9, 0, 1],
|
[58.8, -80.9, 0, 1],
|
||||||
[-58.8, -80.9, 0, 1],
|
[-58.8, -80.9, 0, 1],
|
||||||
[-95.1, 30.9, 0, 1],
|
[-95.1, 30.9, 0, 1],
|
||||||
];
|
] as Point[],
|
||||||
|
faces: [
|
||||||
const faces = [
|
|
||||||
[0, 1, 2],
|
[0, 1, 2],
|
||||||
[0, 2, 3],
|
[0, 2, 3],
|
||||||
[0, 3, 4],
|
[0, 3, 4],
|
||||||
[0, 4, 5],
|
[0, 4, 5],
|
||||||
[0, 5, 1],
|
[0, 5, 1],
|
||||||
[1, 2, 3, 4, 5],
|
[1, 2, 3, 4, 5],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const identityMatrix: Point[] = [
|
||||||
|
[1, 0, 0, 0],
|
||||||
|
[0, 1, 0, 0],
|
||||||
|
[0, 0, 1, 0],
|
||||||
|
[0, 0, 0, 1],
|
||||||
];
|
];
|
||||||
|
|
||||||
// Cube
|
const translationMatrix: Point[] = [
|
||||||
// const points: Point[] = [
|
[1, 0, 0, 0],
|
||||||
// [0, 0, 0, 1],
|
[0, -1, 0, 0],
|
||||||
// [0, 0, 100, 1],
|
[0, 0, 1, 0],
|
||||||
// [0, 100, 0, 1],
|
[centerX, centerY, 0, 1],
|
||||||
// [0, 100, 100, 1],
|
];
|
||||||
// [100, 0, 0, 1],
|
|
||||||
// [100, 0, 100, 1],
|
|
||||||
// [100, 100, 0, 1],
|
|
||||||
// [100, 100, 100, 1],
|
|
||||||
// ];
|
|
||||||
//
|
|
||||||
// const 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],
|
|
||||||
// ];
|
|
||||||
|
|
||||||
// Rotate around X axis
|
// Rotate around X axis
|
||||||
const rotateX = (angle: number): Point[] => {
|
const rotateX = (angle: number): Point[] => {
|
||||||
@@ -129,13 +198,43 @@ onMounted(() => {
|
|||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
const axonometryIsometric = (): Point[] => {
|
const projections = {
|
||||||
return [
|
[Projections.NONE]: (): Point[] => identityMatrix,
|
||||||
|
|
||||||
|
[Projections.ISOMETRIC]: (): Point[] => [
|
||||||
[0.707, -0.408, 0, 0],
|
[0.707, -0.408, 0, 0],
|
||||||
[0, 0.816, 0, 0],
|
[0, 0.816, 0, 0],
|
||||||
[-0.707, -0.408, 1, 0],
|
[-0.707, -0.408, 1, 0],
|
||||||
[0, 0, 0, 1],
|
[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
|
// Multiply array of points by matrix
|
||||||
@@ -193,18 +292,27 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useTransformations((translation, rotation, scale) => {
|
const { currentFigure } = useFigure();
|
||||||
drawFigure(
|
|
||||||
mul([
|
useTransformations((translation, rotation, scale, prj) => {
|
||||||
points,
|
const matrix = mul([
|
||||||
|
figureList[currentFigure.value].points,
|
||||||
scaleMatrix(scale[0], scale[1], scale[2]),
|
scaleMatrix(scale[0], scale[1], scale[2]),
|
||||||
rotate(rotation[0], rotation[1], rotation[2]),
|
rotate(rotation[0], rotation[1], rotation[2]),
|
||||||
translate(translation[0], translation[1], translation[2]),
|
translate(translation[0], translation[1], translation[2]),
|
||||||
rotateX(-90),
|
projections[prj](),
|
||||||
axonometryIsometric(),
|
]);
|
||||||
translate(centerX, centerY + figureSizeH / 2, 0),
|
|
||||||
]),
|
for (let i = 0; i < matrix.length; i++) {
|
||||||
faces
|
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
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,12 +5,14 @@ const {
|
|||||||
label,
|
label,
|
||||||
min,
|
min,
|
||||||
max,
|
max,
|
||||||
|
currentValue,
|
||||||
defaultValue,
|
defaultValue,
|
||||||
step = 0.1,
|
step = 0.1,
|
||||||
} = defineProps<{
|
} = defineProps<{
|
||||||
label: string;
|
label: string;
|
||||||
min: number;
|
min: number;
|
||||||
max: number;
|
max: number;
|
||||||
|
currentValue: number;
|
||||||
defaultValue: number;
|
defaultValue: number;
|
||||||
step?: number;
|
step?: number;
|
||||||
}>();
|
}>();
|
||||||
@@ -19,7 +21,7 @@ const emit = defineEmits<{
|
|||||||
(event: 'change', value: number): void;
|
(event: 'change', value: number): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const value = ref<number>(defaultValue);
|
const value = ref<number>(currentValue);
|
||||||
|
|
||||||
const onChange = (event: Event) => {
|
const onChange = (event: Event) => {
|
||||||
const { target } = event as HTMLElementEvent<HTMLInputElement>;
|
const { target } = event as HTMLElementEvent<HTMLInputElement>;
|
||||||
|
|||||||
20
src/composables/useFigure.ts
Normal file
20
src/composables/useFigure.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
export const enum Figures {
|
||||||
|
CUBE,
|
||||||
|
OCTAHEDRON,
|
||||||
|
TRIHEDRAL_PYRAMID,
|
||||||
|
SQUARE_PYRAMID,
|
||||||
|
PENTAGONAL_PYRAMID,
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentFigure = ref<Figures>(Figures.PENTAGONAL_PYRAMID);
|
||||||
|
|
||||||
|
export const useFigure = () => {
|
||||||
|
const setFigure = (figure: Figures) => {
|
||||||
|
currentFigure.value = figure;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
currentFigure,
|
||||||
|
setFigure,
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -1,10 +1,27 @@
|
|||||||
|
export const enum Projections {
|
||||||
|
NONE,
|
||||||
|
ISOMETRIC,
|
||||||
|
DIMETRIC,
|
||||||
|
TRIMETRIC,
|
||||||
|
ONE_POINT_PERSPECTIVE,
|
||||||
|
TWO_POINT_PERSPECTIVE,
|
||||||
|
}
|
||||||
|
|
||||||
export type XYZ = [number, number, number];
|
export type XYZ = [number, number, number];
|
||||||
|
|
||||||
type fn = ((translation: XYZ, rotation: XYZ, scale: XYZ) => void) | null;
|
type fn =
|
||||||
|
| ((
|
||||||
|
translation: XYZ,
|
||||||
|
rotation: XYZ,
|
||||||
|
scale: XYZ,
|
||||||
|
projection: Projections
|
||||||
|
) => void)
|
||||||
|
| null;
|
||||||
|
|
||||||
const translation = reactive<XYZ>([0, 0, 0]);
|
const translation = reactive<XYZ>([0, 0, 0]);
|
||||||
const rotation = reactive<XYZ>([0, 0, 0]);
|
const rotation = reactive<XYZ>([0, 0, 0]);
|
||||||
const scale = reactive<XYZ>([1, 1, 1]);
|
const scale = reactive<XYZ>([1, 1, 1]);
|
||||||
|
const projection = ref<Projections>(Projections.NONE);
|
||||||
|
|
||||||
const setTranslation = (axis: number, value: number) => {
|
const setTranslation = (axis: number, value: number) => {
|
||||||
translation[axis] = value;
|
translation[axis] = value;
|
||||||
@@ -18,17 +35,23 @@ const setScale = (axis: number, value: number) => {
|
|||||||
scale[axis] = value;
|
scale[axis] = value;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const setProjection = (value: Projections) => {
|
||||||
|
projection.value = value;
|
||||||
|
};
|
||||||
|
|
||||||
export const useTransformations = (onUpdate: fn = null) => {
|
export const useTransformations = (onUpdate: fn = null) => {
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
if (onUpdate) onUpdate(translation, rotation, scale);
|
if (onUpdate) onUpdate(translation, rotation, scale, projection.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
translation,
|
translation,
|
||||||
rotation,
|
rotation,
|
||||||
scale,
|
scale,
|
||||||
|
projection,
|
||||||
setTranslation,
|
setTranslation,
|
||||||
setRotation,
|
setRotation,
|
||||||
setScale,
|
setScale,
|
||||||
|
setProjection,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const showMenu = ref<boolean>(false);
|
const showMenu = ref<boolean>(true);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -1,17 +1,30 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const axes = ['x', 'y', 'z'];
|
const axes = ['x', 'y', 'z'];
|
||||||
const projections = [
|
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 activeProjection = ref<number>(0);
|
||||||
|
|
||||||
const transformations = useTransformations();
|
const transformations = useTransformations();
|
||||||
|
|
||||||
|
onMounted(() => (activeProjection.value = transformations.projection.value));
|
||||||
|
|
||||||
|
const setActiveProjection = (index: number) => {
|
||||||
|
activeProjection.value = index;
|
||||||
|
transformations.setProjection(projections[index].projection);
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -20,9 +33,10 @@ const transformations = useTransformations();
|
|||||||
<FormRange
|
<FormRange
|
||||||
v-for="(axis, i) in axes"
|
v-for="(axis, i) in axes"
|
||||||
:label="`${axis} =`"
|
:label="`${axis} =`"
|
||||||
:min="-100"
|
:min="-500"
|
||||||
:max="100"
|
:max="500"
|
||||||
:step="1"
|
:step="5"
|
||||||
|
:current-value="transformations.translation[i]"
|
||||||
:defaultValue="0"
|
:defaultValue="0"
|
||||||
@change="transformations.setTranslation(i, $event)"
|
@change="transformations.setTranslation(i, $event)"
|
||||||
/>
|
/>
|
||||||
@@ -34,6 +48,7 @@ const transformations = useTransformations();
|
|||||||
:min="0"
|
:min="0"
|
||||||
:max="359"
|
:max="359"
|
||||||
:step="1"
|
:step="1"
|
||||||
|
:current-value="transformations.rotation[i]"
|
||||||
:defaultValue="0"
|
:defaultValue="0"
|
||||||
@change="transformations.setRotation(i, $event)"
|
@change="transformations.setRotation(i, $event)"
|
||||||
/>
|
/>
|
||||||
@@ -46,6 +61,7 @@ const transformations = useTransformations();
|
|||||||
:min="0.1"
|
:min="0.1"
|
||||||
:max="5"
|
:max="5"
|
||||||
:step="0.1"
|
:step="0.1"
|
||||||
|
:current-value="transformations.scale[i]"
|
||||||
:defaultValue="1"
|
:defaultValue="1"
|
||||||
@change="transformations.setScale(i, $event)"
|
@change="transformations.setScale(i, $event)"
|
||||||
/>
|
/>
|
||||||
@@ -54,10 +70,10 @@ const transformations = useTransformations();
|
|||||||
<GridContainer>
|
<GridContainer>
|
||||||
<GridElement
|
<GridElement
|
||||||
v-for="(projection, i) in projections"
|
v-for="(projection, i) in projections"
|
||||||
:key="projection"
|
:key="projection.title"
|
||||||
:is-active="activeProjection === i"
|
:is-active="activeProjection === i"
|
||||||
:title="projection"
|
:title="projection.title"
|
||||||
@click="activeProjection = i"
|
@click="setActiveProjection(i)"
|
||||||
/>
|
/>
|
||||||
</GridContainer>
|
</GridContainer>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
|
|||||||
@@ -1,10 +1,34 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const figures = ['Тетраэдр', 'Гексаэдр', 'Октаэдр', 'Додекаэдр', 'Икосаэдр'];
|
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) => {
|
||||||
|
activeFigure.value = index;
|
||||||
|
figure.setFigure(allFigures[index].figure);
|
||||||
|
navigateTo('/figure');
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<h1>Выберете фигуру</h1>
|
<h1>Выберете фигуру</h1>
|
||||||
<GridContainer>
|
<GridContainer>
|
||||||
<GridElement v-for="figure in figures" :key="figure" :title="figure" />
|
<GridElement
|
||||||
|
v-for="(figure, i) in allFigures"
|
||||||
|
:key="figure.title"
|
||||||
|
:title="figure.title"
|
||||||
|
:is-active="activeFigure === i"
|
||||||
|
@click="selectFigure(i)"
|
||||||
|
/>
|
||||||
</GridContainer>
|
</GridContainer>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
Reference in New Issue
Block a user