diff --git a/src/app/components/Octicon.tsx b/src/app/components/Octicon.tsx index 16cd01b2..a997264b 100644 --- a/src/app/components/Octicon.tsx +++ b/src/app/components/Octicon.tsx @@ -3,6 +3,7 @@ export const Octicon = { archive: , arrow_left: , arrow_right: , + arrow_switch: , check: , chevron_down: , chevron_left: , diff --git a/src/app/pages/Transformation.tsx b/src/app/pages/Transformation.tsx index 171ebfe7..5593cb13 100644 --- a/src/app/pages/Transformation.tsx +++ b/src/app/pages/Transformation.tsx @@ -1,5 +1,5 @@ import { Matrix3, Matrix4, Mesh, Quad, Renderer, ShaderProgram, Vector, Vertex } from 'deepslate' -import { mat4, quat } from 'gl-matrix' +import { mat4, quat, vec3 } from 'gl-matrix' import { useCallback, useMemo, useRef, useState } from 'preact/hooks' import { Footer, NumberInput, Octicon, RangeInput } from '../components/index.js' import { InteractiveCanvas3D } from '../components/previews/InteractiveCanvas3D.jsx' @@ -12,6 +12,11 @@ import { composeMatrix, svdDecompose } from '../Utils.js' const XYZ = ['x', 'y', 'z'] as const type XYZ = typeof XYZ[number] +const XYZW = ['x', 'y', 'z', 'w'] as const + +const RotationModes = ['quaternion', 'axis_angle'] as const +type RotationMode = typeof RotationModes[number] + interface Props { path?: string, } @@ -104,8 +109,41 @@ export function Transformation({}: Props) { const copy = quat.clone(rightRotation) copy[i] = value if (normalizeRight) quat.normalize(copy, copy) - setRightRotation(copy) - setMatrix(composeMatrix(translation, leftRotation, scale, rightRotation)) + updateRightRotation(copy) + }, [rightRotation, normalizeRight, updateRightRotation]) + + const [rotationMode, setRotationMode] = useState('quaternion') + + const leftRotationAxisAngle = useMemo(() => { + const axis = vec3.create() + const angle = quat.getAxisAngle(axis, leftRotation) + return { axis, angle } + }, [leftRotation]) + + const changeLeftRotationAxisAngle = useCallback((i: number, value: number) => { + const axisCopy = vec3.clone(leftRotationAxisAngle.axis) + if (i < 3) axisCopy[i] = value + else leftRotationAxisAngle.angle = value + if (normalizeLeft) vec3.normalize(axisCopy, axisCopy) + const copy = quat.setAxisAngle(quat.create(), axisCopy, leftRotationAxisAngle.angle) + if (normalizeLeft) quat.normalize(copy, copy) + updateLeftRotation(copy) + }, [leftRotation, normalizeLeft, updateLeftRotation]) + + const rightRotationAxisAngle = useMemo(() => { + const axis = vec3.create() + const angle = quat.getAxisAngle(axis, rightRotation) + return { axis, angle } + }, [rightRotation]) + + const changeRightRotationAxisAngle = useCallback((i: number, value: number) => { + const axisCopy = vec3.clone(rightRotationAxisAngle.axis) + if (i < 3) axisCopy[i] = value + else rightRotationAxisAngle.angle = value + if (normalizeRight) vec3.normalize(axisCopy, axisCopy) + const copy = quat.setAxisAngle(quat.create(), axisCopy, rightRotationAxisAngle.angle) + if (normalizeRight) quat.normalize(copy, copy) + updateRightRotation(copy) }, [rightRotation, normalizeRight, updateRightRotation]) const renderer = useRef() @@ -143,42 +181,50 @@ export function Transformation({}: Props) { - {XYZ.map((c) =>
- changeTranslation(c, v)} /> - changeTranslation(c, v)} /> -
)} + {XYZ.map((c) => + changeTranslation(c, v)} /> + )}
{locale('transformation.left_rotation')} +
- {Array(4).fill(0).map((_, i) =>
- changeLeftRotation(i, v)} /> - changeLeftRotation(i, v)} /> -
)} + {rotationMode === 'quaternion' + ? XYZW.map((c, i) => + changeLeftRotation(i, v)} />) + : <> + {XYZ.map((c, i) => + changeLeftRotationAxisAngle(i, v)} />)} + changeLeftRotationAxisAngle(3, v)} /> + }
{locale('transformation.scale')}
- {XYZ.map((c) =>
- changeScale(c, v)} /> - changeScale(c, v)} /> -
)} + {XYZ.map((c) => + changeScale(c, v)} /> + )}
{locale('transformation.right_rotation')} +
- {Array(4).fill(0).map((_, i) =>
- changeRightRotation(i, v)} /> - changeRightRotation(i, v)} /> -
)} + {rotationMode === 'quaternion' + ? XYZW.map((c, i) => + changeRightRotation(i, v)} />) + : <> + {XYZ.map((c, i) => + changeRightRotationAxisAngle(i, v)}/>)} + changeRightRotationAxisAngle(3, v)} /> + }
@@ -189,10 +235,9 @@ export function Transformation({}: Props) {
- {Array(16).fill(0).map((_, i) =>
- changeMatrix(i, v)} /> - changeMatrix(i, v)} /> -
)} + {Array(16).fill(0).map((_, i) => + changeMatrix(i, v)} /> + )} @@ -205,6 +250,21 @@ export function Transformation({}: Props) { } +interface SliderProps { + label?: string + value: number + onChange?: (value: number) => void + min?: number + max?: number +} +function Slider({ label, value, onChange, min, max }: SliderProps) { + return
+ {label && } + + +
+} + function formatFloat(x: number) { return x.toFixed(3).replace(/\.?0+$/, '') + 'f' } diff --git a/src/locales/en.json b/src/locales/en.json index 6cc0f571..4fb0dfde 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -136,6 +136,9 @@ "transformation.right_rotation": "Right rotation", "transformation.copy_decomposed": "Copy decomposed format", "transformation.copy_composed": "Copy matrix format", + "transformation.rotation_mode": "Format: %0%", + "transformation.rotation_mode.quaternion": "Quaternion", + "transformation.rotation_mode.axis_angle": "Axis-angle", "trim_material": "Trim material", "trim_pattern": "Trim pattern", "pack_mcmeta": "Pack.mcmeta", diff --git a/src/styles/global.css b/src/styles/global.css index 86780f89..19ce75b8 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -584,13 +584,21 @@ main.has-project { margin-bottom: 2px; } +.transformation-input > * + * { + margin-left: 8px; +} + +.transformation-input label { + color: var(--text-3); + font-family: consolas; +} + .transformation-input input[type=number] { width: 100px; padding: 3px 6px; border: none; border-radius: 3px; font-size: 14px; - margin-right: 8px; background-color: var(--background-2); color: var(--text-2); }