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,48 +21,117 @@ onMounted(() => {
|
||||
const centerX = sizeX / 2;
|
||||
const centerY = sizeY / 2;
|
||||
|
||||
const figureSizeH = 200;
|
||||
const figureSizeW = 200;
|
||||
const figureSize = 200;
|
||||
|
||||
const points: Point[] = [
|
||||
[0, 0, figureSizeH, 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],
|
||||
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 faces = [
|
||||
[0, 1, 2],
|
||||
[0, 2, 3],
|
||||
[0, 3, 4],
|
||||
[0, 4, 5],
|
||||
[0, 5, 1],
|
||||
[1, 2, 3, 4, 5],
|
||||
const translationMatrix: Point[] = [
|
||||
[1, 0, 0, 0],
|
||||
[0, -1, 0, 0],
|
||||
[0, 0, 1, 0],
|
||||
[centerX, centerY, 0, 1],
|
||||
];
|
||||
|
||||
// Cube
|
||||
// const points: Point[] = [
|
||||
// [0, 0, 0, 1],
|
||||
// [0, 0, 100, 1],
|
||||
// [0, 100, 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
|
||||
const rotateX = (angle: number): Point[] => {
|
||||
const rad = (angle * Math.PI) / 180;
|
||||
@@ -129,13 +198,43 @@ onMounted(() => {
|
||||
];
|
||||
};
|
||||
|
||||
const axonometryIsometric = (): Point[] => {
|
||||
return [
|
||||
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
|
||||
@@ -193,18 +292,27 @@ onMounted(() => {
|
||||
}
|
||||
};
|
||||
|
||||
useTransformations((translation, rotation, scale) => {
|
||||
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([
|
||||
points,
|
||||
scaleMatrix(scale[0], scale[1], scale[2]),
|
||||
rotate(rotation[0], rotation[1], rotation[2]),
|
||||
translate(translation[0], translation[1], translation[2]),
|
||||
rotateX(-90),
|
||||
axonometryIsometric(),
|
||||
translate(centerX, centerY + figureSizeH / 2, 0),
|
||||
]),
|
||||
faces
|
||||
mul([matrix, translationMatrix]),
|
||||
figureList[currentFigure.value].faces
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,12 +5,14 @@ const {
|
||||
label,
|
||||
min,
|
||||
max,
|
||||
currentValue,
|
||||
defaultValue,
|
||||
step = 0.1,
|
||||
} = defineProps<{
|
||||
label: string;
|
||||
min: number;
|
||||
max: number;
|
||||
currentValue: number;
|
||||
defaultValue: number;
|
||||
step?: number;
|
||||
}>();
|
||||
@@ -19,7 +21,7 @@ const emit = defineEmits<{
|
||||
(event: 'change', value: number): void;
|
||||
}>();
|
||||
|
||||
const value = ref<number>(defaultValue);
|
||||
const value = ref<number>(currentValue);
|
||||
|
||||
const onChange = (event: Event) => {
|
||||
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];
|
||||
|
||||
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 rotation = reactive<XYZ>([0, 0, 0]);
|
||||
const scale = reactive<XYZ>([1, 1, 1]);
|
||||
const projection = ref<Projections>(Projections.NONE);
|
||||
|
||||
const setTranslation = (axis: number, value: number) => {
|
||||
translation[axis] = value;
|
||||
@@ -18,17 +35,23 @@ const setScale = (axis: number, value: number) => {
|
||||
scale[axis] = value;
|
||||
};
|
||||
|
||||
const setProjection = (value: Projections) => {
|
||||
projection.value = value;
|
||||
};
|
||||
|
||||
export const useTransformations = (onUpdate: fn = null) => {
|
||||
watchEffect(() => {
|
||||
if (onUpdate) onUpdate(translation, rotation, scale);
|
||||
if (onUpdate) onUpdate(translation, rotation, scale, projection.value);
|
||||
});
|
||||
|
||||
return {
|
||||
translation,
|
||||
rotation,
|
||||
scale,
|
||||
projection,
|
||||
setTranslation,
|
||||
setRotation,
|
||||
setScale,
|
||||
setProjection,
|
||||
};
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
const showMenu = ref<boolean>(false);
|
||||
const showMenu = ref<boolean>(true);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -1,17 +1,30 @@
|
||||
<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();
|
||||
|
||||
onMounted(() => (activeProjection.value = transformations.projection.value));
|
||||
|
||||
const setActiveProjection = (index: number) => {
|
||||
activeProjection.value = index;
|
||||
transformations.setProjection(projections[index].projection);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -20,9 +33,10 @@ const transformations = useTransformations();
|
||||
<FormRange
|
||||
v-for="(axis, i) in axes"
|
||||
:label="`${axis} =`"
|
||||
:min="-100"
|
||||
:max="100"
|
||||
:step="1"
|
||||
:min="-500"
|
||||
:max="500"
|
||||
:step="5"
|
||||
:current-value="transformations.translation[i]"
|
||||
:defaultValue="0"
|
||||
@change="transformations.setTranslation(i, $event)"
|
||||
/>
|
||||
@@ -34,6 +48,7 @@ const transformations = useTransformations();
|
||||
:min="0"
|
||||
:max="359"
|
||||
:step="1"
|
||||
:current-value="transformations.rotation[i]"
|
||||
:defaultValue="0"
|
||||
@change="transformations.setRotation(i, $event)"
|
||||
/>
|
||||
@@ -46,6 +61,7 @@ const transformations = useTransformations();
|
||||
:min="0.1"
|
||||
:max="5"
|
||||
:step="0.1"
|
||||
:current-value="transformations.scale[i]"
|
||||
:defaultValue="1"
|
||||
@change="transformations.setScale(i, $event)"
|
||||
/>
|
||||
@@ -54,10 +70,10 @@ const transformations = useTransformations();
|
||||
<GridContainer>
|
||||
<GridElement
|
||||
v-for="(projection, i) in projections"
|
||||
:key="projection"
|
||||
:key="projection.title"
|
||||
:is-active="activeProjection === i"
|
||||
:title="projection"
|
||||
@click="activeProjection = i"
|
||||
:title="projection.title"
|
||||
@click="setActiveProjection(i)"
|
||||
/>
|
||||
</GridContainer>
|
||||
</Accordion>
|
||||
|
||||
@@ -1,10 +1,34 @@
|
||||
<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>
|
||||
|
||||
<template>
|
||||
<h1>Выберете фигуру</h1>
|
||||
<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>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user