Initial working version of transformation preview

- missing ability to modify matrix
- sdvDecompose is still probably wrong
- missing ability to edit rotations using axis + angle
This commit is contained in:
Misode
2023-02-09 18:03:15 +01:00
parent 106323186c
commit 03ddf6011b
8 changed files with 536 additions and 3 deletions

View File

@@ -4,7 +4,7 @@ import '../styles/global.css'
import '../styles/nodes.css'
import { Analytics } from './Analytics.js'
import { Header } from './components/index.js'
import { Changelog, Generator, Generators, Guide, Guides, Home, Partners, Sounds, Versions, Worldgen } from './pages/index.js'
import { Changelog, Generator, Generators, Guide, Guides, Home, Partners, Sounds, Transformation, Versions, Worldgen } from './pages/index.js'
import { cleanUrl } from './Utils.js'
export function App() {
@@ -24,6 +24,7 @@ export function App() {
<Sounds path="/sounds" />
<Changelog path="/changelog" />
<Versions path="/versions" />
<Transformation path="/transformation" />
<Guides path="/guides" />
<Guide path="/guides/:id" />
<Generator default />

View File

@@ -2,8 +2,7 @@ import type { DataModel } from '@mcschema/core'
import { Path } from '@mcschema/core'
import * as zip from '@zip.js/zip.js'
import type { Random } from 'deepslate/core'
import type { mat3 } from 'gl-matrix'
import { vec2 } from 'gl-matrix'
import { mat3, mat4, quat, vec2, vec3 } from 'gl-matrix'
import yaml from 'js-yaml'
import { route } from 'preact-router'
import rfdc from 'rfdc'
@@ -377,3 +376,178 @@ export function iterateWorld2D<D>(img: ImageData, transform: mat3, getData: (x:
img.data[4 * i + 3] = 255
}
}
function makeFloat(a: number) {
return a > 3.4028235E38 ? Infinity : a < -3.4028235E38 ? -Infinity : a
}
const G = 3 + 2 * Math.sqrt(2)
const CS = Math.cos(Math.PI / 8)
const SS = Math.sin(Math.PI / 8)
function approxGivensQuat(a: number, b: number, c: number): [number, number] {
const d = 2 * (a - c)
if (makeFloat(G * b * b) < makeFloat(d * d)) {
const e = 1 / Math.sqrt(b * b + d * d)
return [e * b, e * d]
} else {
return [SS, CS]
}
}
function qrGivensQuat(a: number, b: number) {
const c = Math.hypot(a, b)
let d = c > 1e-6 ? b : 0
let e = Math.abs(a) + Math.max(c, 1e-6)
if (a < 0) {
[d, e] = [e, d]
}
const f = 1 / Math.sqrt(e * e + d * d)
return [d * f, e * f]
}
// modifies the passed mat3
function stepJacobi(m: mat3): quat {
const n = mat3.create()
const q = quat.create()
if (m[1] * m[1] + m[3] * m[3] > 1e-6) {
const [a, b] = approxGivensQuat(m[0], 0.5 * (m[1] + m[3]), m[4])
const r = quat.fromValues(0, 0, a, b)
const c = b * b - a * a
const d = -2 * a * b
const e = b * b + a * a
quat.mul(q, q, r)
n[0] = c
n[4] = c
n[1] = -d
n[3] = d
n[8] = e
mat3.mul(m, m, n)
mat3.transpose(n, n)
mat3.mul(n, n, m)
mat3.copy(m, n)
}
// console.log('J1', q, m)
if (m[2] * m[2] + m[6] * m[6] > 1e-6) {
const pair = approxGivensQuat(m[0], 0.5 * (m[2] + m[6]), m[8])
const a = -pair[0]
const b = pair[1]
const r = quat.fromValues(0, a, 0, b)
const c = b * b - a * a
const d = -2 * a * b
const e = b * b + a * a
quat.mul(q, q, r)
n[0] = c
n[8] = c
n[2] = d
n[6] = -d
n[4] = e
mat3.mul(m, m, n)
mat3.transpose(n, n)
mat3.mul(n, n, m)
mat3.copy(m, n)
}
// console.log('J2', q, m)
if (m[5] * m[5] + m[7] * m[7] > 1e-6) {
const [a, b] = approxGivensQuat(m[4], 0.5 * (m[5] + m[7]), m[8])
const r = quat.fromValues(a, 0, 0, b)
const c = b * b - a * a
const d = -2 * a * b
const e = b * b + a * a
quat.mul(q, q, r)
n[4] = c
n[8] = c
n[5] = -d
n[7] = d
n[0] = e
mat3.mul(m, m, n)
mat3.transpose(n, n)
mat3.mul(n, n, m)
mat3.copy(m, n)
}
// console.log('J3', q, m)
return q
}
export function svdDecompose(m: mat3): [quat, vec3, quat] {
const q = quat.create()
const r = quat.create()
const n = mat3.create()
mat3.transpose(n, m)
mat3.mul(n, n, m)
// console.log('A', n)
for (let i = 0; i < 5; i += 1) {
quat.mul(r, r, stepJacobi(n))
}
quat.normalize(r, r)
// console.log('B', r)
const p0 = mat3.create()
mat3.fromQuat(p0, r)
mat3.mul(p0, m, p0)
// console.log('C', p0)
let f = 1
const [a1, b1] = qrGivensQuat(p0[0], p0[1])
const c1 = b1 * b1 - a1 * a1
const d1 = -2 * a1 * b1
const e1 = b1 * b1 + a1 * a1
const s1 = quat.fromValues(0, 0, a1, b1)
// console.log('D', s1)
quat.mul(q, q, s1)
const p1 = mat3.create()
p1[0] = c1
p1[4] = c1
p1[1] = d1
p1[3] = -d1
p1[8] = e1
f *= e1
mat3.mul(p1, p1, p0)
// console.log('E', p1)
const pair = qrGivensQuat(p1[0], p1[2])
const a2 = -pair[0]
const b2 = pair[1]
const c2 = b2 * b2 - a2 * a2
const d2 = -2 * a2 * b2
const e2 = b2 * b2 + a2 * a2
const s2 = quat.fromValues(0, a2, 0, b2)
quat.mul(q, q, s2)
const p2 = mat3.create()
p2[0] = c2
p2[8] = c2
p2[2] = -d2
p2[6] = d2
p2[4] = e2
f *= e2
// console.log('H2', f, e2)
mat3.mul(p2, p2, p1)
const [a3, b3] = qrGivensQuat(p2[4], p2[5])
const c3 = b3 * b3 - a3 * a3
const d3 = -2 * a3 * b3
const e3 = b3 * b3 + a3 * a3
const s3 = quat.fromValues(a3, 0, 0, b3)
quat.mul(q, q, s3)
const p3 = mat3.create()
p3[4] = c3
p3[8] = c3
p3[5] = d3
p3[7] = -d3
p3[0] = e3
f *= e3
mat3.mul(p3, p3, p2)
// console.log('G', p1)
f = 1 / f
quat.scale(q, q, Math.sqrt(f))
const scale = vec3.fromValues(p3[0] * f, p3[4] * f, p3[8] * f)
return [q, scale, r]
}
export function toAffine(m: mat4): mat4 {
if (m[15] === 0) m[15] = 1
const a = 1 / m[15]
const n = mat4.clone(m)
mat4.scale(n, n, [a, a, a])
return n
}

View File

@@ -32,6 +32,7 @@ export const Octicon = {
kebab_horizontal: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path d="M8 9a1.5 1.5 0 100-3 1.5 1.5 0 000 3zM1.5 9a1.5 1.5 0 100-3 1.5 1.5 0 000 3zm13 0a1.5 1.5 0 100-3 1.5 1.5 0 000 3z"></path></svg>,
link: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg>,
link_external: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M10.604 1h4.146a.25.25 0 01.25.25v4.146a.25.25 0 01-.427.177L13.03 4.03 9.28 7.78a.75.75 0 01-1.06-1.06l3.75-3.75-1.543-1.543A.25.25 0 0110.604 1zM3.75 2A1.75 1.75 0 002 3.75v8.5c0 .966.784 1.75 1.75 1.75h8.5A1.75 1.75 0 0014 12.25v-3.5a.75.75 0 00-1.5 0v3.5a.25.25 0 01-.25.25h-8.5a.25.25 0 01-.25-.25v-8.5a.25.25 0 01.25-.25h3.5a.75.75 0 000-1.5h-3.5z"></path></svg>,
lock: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path d="M4 4a4 4 0 0 1 8 0v2h.25c.966 0 1.75.784 1.75 1.75v5.5A1.75 1.75 0 0 1 12.25 15h-8.5A1.75 1.75 0 0 1 2 13.25v-5.5C2 6.784 2.784 6 3.75 6H4Zm8.25 3.5h-8.5a.25.25 0 0 0-.25.25v5.5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-5.5a.25.25 0 0 0-.25-.25ZM10.5 6V4a2.5 2.5 0 1 0-5 0v2Z"></path></svg>,
mark_github: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"></path></svg>,
moon: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M9.598 1.591a.75.75 0 01.785-.175 7 7 0 11-8.967 8.967.75.75 0 01.961-.96 5.5 5.5 0 007.046-7.046.75.75 0 01.175-.786zm1.616 1.945a7 7 0 01-7.678 7.678 5.5 5.5 0 107.678-7.678z"></path></svg>,
package: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M8.878.392a1.75 1.75 0 00-1.756 0l-5.25 3.045A1.75 1.75 0 001 4.951v6.098c0 .624.332 1.2.872 1.514l5.25 3.045a1.75 1.75 0 001.756 0l5.25-3.045c.54-.313.872-.89.872-1.514V4.951c0-.624-.332-1.2-.872-1.514L8.878.392zM7.875 1.69a.25.25 0 01.25 0l4.63 2.685L8 7.133 3.245 4.375l4.63-2.685zM2.5 5.677v5.372c0 .09.047.171.125.216l4.625 2.683V8.432L2.5 5.677zm6.25 8.271l4.625-2.683a.25.25 0 00.125-.216V5.677L8.75 8.432v5.516z"></path></svg>,
@@ -55,6 +56,7 @@ export const Octicon = {
three_bars: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M1 2.75A.75.75 0 011.75 2h12.5a.75.75 0 110 1.5H1.75A.75.75 0 011 2.75zm0 5A.75.75 0 011.75 7h12.5a.75.75 0 110 1.5H1.75A.75.75 0 011 7.75zM1.75 12a.75.75 0 100 1.5h12.5a.75.75 0 100-1.5H1.75z"></path></svg>,
trashcan: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M6.5 1.75a.25.25 0 01.25-.25h2.5a.25.25 0 01.25.25V3h-3V1.75zm4.5 0V3h2.25a.75.75 0 010 1.5H2.75a.75.75 0 010-1.5H5V1.75C5 .784 5.784 0 6.75 0h2.5C10.216 0 11 .784 11 1.75zM4.496 6.675a.75.75 0 10-1.492.15l.66 6.6A1.75 1.75 0 005.405 15h5.19c.9 0 1.652-.681 1.741-1.576l.66-6.6a.75.75 0 00-1.492-.149l-.66 6.6a.25.25 0 01-.249.225h-5.19a.25.25 0 01-.249-.225l-.66-6.6z"></path></svg>,
unfold: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path d="M8.177.677l2.896 2.896a.25.25 0 01-.177.427H8.75v1.25a.75.75 0 01-1.5 0V4H5.104a.25.25 0 01-.177-.427L7.823.677a.25.25 0 01.354 0zM7.25 10.75a.75.75 0 011.5 0V12h2.146a.25.25 0 01.177.427l-2.896 2.896a.25.25 0 01-.354 0l-2.896-2.896A.25.25 0 015.104 12H7.25v-1.25zm-5-2a.75.75 0 000-1.5h-.5a.75.75 0 000 1.5h.5zM6 8a.75.75 0 01-.75.75h-.5a.75.75 0 010-1.5h.5A.75.75 0 016 8zm2.25.75a.75.75 0 000-1.5h-.5a.75.75 0 000 1.5h.5zM12 8a.75.75 0 01-.75.75h-.5a.75.75 0 010-1.5h.5A.75.75 0 0112 8zm2.25.75a.75.75 0 000-1.5h-.5a.75.75 0 000 1.5h.5z"></path></svg>,
unlock: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path d="M5.5 4v2h7A1.5 1.5 0 0 1 14 7.5v6a1.5 1.5 0 0 1-1.5 1.5h-9A1.5 1.5 0 0 1 2 13.5v-6A1.5 1.5 0 0 1 3.499 6H4V4a4 4 0 0 1 7.371-2.154.75.75 0 0 1-1.264.808A2.5 2.5 0 0 0 5.5 4Zm-2 3.5v6h9v-6h-9Z"></path></svg>,
upload: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M8.53 1.22a.75.75 0 00-1.06 0L3.72 4.97a.75.75 0 001.06 1.06l2.47-2.47v6.69a.75.75 0 001.5 0V3.56l2.47 2.47a.75.75 0 101.06-1.06L8.53 1.22zM3.75 13a.75.75 0 000 1.5h8.5a.75.75 0 000-1.5h-8.5z"></path></svg>,
x: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M3.72 3.72a.75.75 0 011.06 0L8 6.94l3.22-3.22a.75.75 0 111.06 1.06L9.06 8l3.22 3.22a.75.75 0 11-1.06 1.06L8 9.06l-3.22 3.22a.75.75 0 01-1.06-1.06L6.94 8 3.72 4.78a.75.75 0 010-1.06z"></path></svg>,
x_circle: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M3.404 12.596a6.5 6.5 0 119.192-9.192 6.5 6.5 0 01-9.192 9.192zM2.344 2.343a8 8 0 1011.313 11.314A8 8 0 002.343 2.343zM6.03 4.97a.75.75 0 00-1.06 1.06L6.94 8 4.97 9.97a.75.75 0 101.06 1.06L8 9.06l1.97 1.97a.75.75 0 101.06-1.06L9.06 8l1.97-1.97a.75.75 0 10-1.06-1.06L8 6.94 6.03 4.97z"></path></svg>,

View File

@@ -92,6 +92,9 @@ function Tools() {
<ToolCard title="Minecraft Sounds" icon="sounds"
link="/sounds/"
desc="Browse through and mix all the vanilla sounds" />
<ToolCard title="Transformation preview"
link="/transformation/"
desc="Visualize transformations for display entities" />
<ToolCard title="Data Pack Upgrader"
link="https://misode.github.io/upgrader/"
desc="Convert your data packs from 1.16 to 1.19" />

View File

@@ -0,0 +1,288 @@
import type { Color } from 'deepslate'
import { Mesh, Quad, Renderer, ShaderProgram, Vector } from 'deepslate'
import { mat3, mat4, quat, vec3 } from 'gl-matrix'
import { useCallback, useEffect, useMemo, useRef, useState } from 'preact/hooks'
import { Footer, NumberInput, Octicon, RangeInput } from '../components/index.js'
import { InteractiveCanvas3D } from '../components/previews/InteractiveCanvas3D.jsx'
import { useLocale, useTitle } from '../contexts/index.js'
import { svdDecompose, toAffine } from '../Utils.js'
interface Props {
path?: string,
}
export function Transformation({}: Props) {
const { locale } = useLocale()
useTitle(locale('title.transformation'))
const [translation, setTranslation] = useState<vec3>(vec3.create())
const [leftRotation, setLeftRotation] = useState<quat>(quat.create())
const [scale, setScale] = useState<vec3>(vec3.fromValues(1, 1, 1))
const [rightRotation, setRightRotation] = useState<quat>(quat.create())
const [normalizeLeft, setNormalizeLeft] = useState(true)
const [normalizeRight, setNormalizeRight] = useState(true)
useEffect(() => {
if (normalizeLeft) setLeftRotation(q => quat.normalize(quat.clone(q), q))
}, [normalizeLeft])
useEffect(() => {
if (normalizeRight) setRightRotation(q => quat.normalize(quat.clone(q), q))
}, [normalizeRight])
const matrix = useMemo(() => {
const m = mat4.create()
mat4.translate(m, m, translation)
mat4.mul(m, m, mat4.fromQuat(mat4.create(), leftRotation))
mat4.scale(m, m, scale)
mat4.mul(m, m, mat4.fromQuat(mat4.create(), rightRotation))
return m
}, [translation, leftRotation, scale, rightRotation])
const setMatrix = useCallback((m: mat4) => {
const affine = toAffine(m)
const newTranslation = mat4.getTranslation(vec3.create(), affine)
const [newLeftRotation, newScale, newRightRotation] = svdDecompose(mat3.fromMat4(mat3.create(), affine))
setTranslation(newTranslation)
setLeftRotation(newLeftRotation)
setScale(newScale)
setRightRotation(newRightRotation)
}, [])
const changeMatrix = useCallback((i: number, value: number) => {
const m = mat4.clone(matrix)
m[i] = value
setMatrix(m)
}, [matrix])
const changeTranslation = useCallback((i: number, value: number) => {
const copy = vec3.clone(translation)
copy[i] = value
setTranslation(copy)
}, [translation])
const changeLeftRotation = useCallback((i: number, value: number) => {
const copy = quat.clone(leftRotation)
copy[i] = value
if (normalizeLeft) {
quat.normalize(copy, copy)
}
setLeftRotation(copy)
}, [leftRotation, normalizeLeft])
const changeScale = useCallback((i: number, value: number) => {
const copy = vec3.clone(scale)
copy[i] = value
setScale(copy)
}, [scale])
const changeRightRotation = useCallback((i: number, value: number) => {
const copy = quat.clone(rightRotation)
copy[i] = value
if (normalizeRight) {
quat.normalize(copy, copy)
}
setRightRotation(copy)
}, [rightRotation, normalizeRight])
const renderer = useRef<MeshRenderer>()
const onSetup = useCallback((canvas: HTMLCanvasElement) => {
const gl = canvas.getContext('webgl')
if (!gl) return
renderer.current = new MeshRenderer(gl)
}, [])
const onResize = useCallback((width: number, height: number) => {
renderer.current?.setViewport(0, 0, width, height)
}, [])
const onDraw = useCallback((view: mat4) => {
renderer.current?.draw(view, matrix)
}, [matrix])
return <main class="has-preview">
<div class="transformation-editor">
<div class="transformation-decomposition">
<div class="transformation-section">
<div class="transformation-title">
<span>{locale('transformation.translation')}</span>
<button class="tooltipped tip-se" aria-label={locale('reset')} onClick={() => setTranslation(vec3.create())}>{Octicon['history']}</button>
</div>
{Array(3).fill(0).map((_, i) => <div class="transformation-input">
<NumberInput value={translation[i].toFixed(3)} onChange={v => changeTranslation(i, v)} />
<RangeInput min={-1} max={1} step={0.01} value={translation[i]} onChange={v => changeTranslation(i, v)} />
</div>)}
</div>
<div class="transformation-section">
<div class="transformation-title">
<span>{locale('transformation.left_rotation')}</span>
<button class="tooltipped tip-se" aria-label={locale('reset')} onClick={() => setLeftRotation(quat.create())}>{Octicon['history']}</button>
<button class="tooltipped tip-se" aria-label={locale('normalize')} onClick={() => setNormalizeLeft(!normalizeLeft)}>{Octicon[normalizeLeft ? 'lock' : 'unlock']}</button>
</div>
{Array(4).fill(0).map((_, i) => <div class="transformation-input">
<NumberInput value={leftRotation[i].toFixed(3)} onChange={v => changeLeftRotation(i, v)} />
<RangeInput min={-1} max={1} step={0.01} value={leftRotation[i]} onChange={v => changeLeftRotation(i, v)} />
</div>)}
</div>
<div class="transformation-section">
<div class="transformation-title">
<span>{locale('transformation.scale')}</span>
<button class="tooltipped tip-se" aria-label={locale('reset')} onClick={() => setScale(vec3.fromValues(1, 1, 1))}>{Octicon['history']}</button>
</div>
{Array(3).fill(0).map((_, i) => <div class="transformation-input">
<NumberInput value={scale[i].toFixed(3)} onChange={v => changeScale(i, v)} />
<RangeInput min={-1} max={1} step={0.01} value={scale[i]} onChange={v => changeScale(i, v)} />
</div>)}
</div>
<div class="transformation-section">
<div class="transformation-title">
<span>{locale('transformation.right_rotation')}</span>
<button class="tooltipped tip-se" aria-label={locale('reset')} onClick={() => setRightRotation(quat.create())}>{Octicon['history']}</button>
<button class="tooltipped tip-se" aria-label={locale('normalize')} onClick={() => setNormalizeRight(!normalizeRight)}>{Octicon[normalizeRight ? 'lock' : 'unlock']}</button>
</div>
{Array(4).fill(0).map((_, i) => <div class="transformation-input">
<NumberInput value={rightRotation[i].toFixed(3)} onChange={v => changeRightRotation(i, v)} />
<RangeInput min={-1} max={1} step={0.01} value={rightRotation[i]} onChange={v => changeRightRotation(i, v)} />
</div>)}
</div>
</div>
<div class="transformation-matrix">
<div class="transformation-section">
<div class="transformation-title">
<span>{locale('transformation.matrix')}</span>
<button class="tooltipped tip-se" aria-label={locale('reset')} onClick={() => setMatrix(mat4.create())}>{Octicon['history']}</button>
</div>
{Array(16).fill(0).map((_, i) => <div class="transformation-input">
<NumberInput value={matrix[i].toFixed(3)} onChange={v => changeMatrix(i, v)} readonly disabled />
<RangeInput min={-1} max={1} step={0.01} value={matrix[i]} onChange={v => changeMatrix(i, v)} readonly disabled />
</div>)}
</div>
</div>
</div>
<div class="popup-preview shown">
<div class="transformation-preview full-preview">
<InteractiveCanvas3D onSetup={onSetup} onResize={onResize} onDraw={onDraw} />
</div>
</div>
<Footer />
</main>
}
const vsMesh = `
attribute vec4 vertPos;
attribute vec3 vertColor;
attribute vec3 normal;
uniform mat4 mView;
uniform mat4 mProj;
varying highp vec3 vColor;
varying highp float vLighting;
void main(void) {
gl_Position = mProj * mView * vertPos;
vColor = vertColor;
vLighting = normal.y * 0.2 + abs(normal.z) * 0.1 + 0.8;
}
`
const fsMesh = `
precision highp float;
varying highp vec3 vColor;
varying highp float vLighting;
void main(void) {
gl_FragColor = vec4(vColor.xyz * vLighting, 1.0);
}
`
const vsGrid = `
attribute vec4 vertPos;
attribute vec3 vertColor;
uniform mat4 mView;
uniform mat4 mProj;
varying highp vec3 vColor;
void main(void) {
gl_Position = mProj * mView * vertPos;
vColor = vertColor;
}
`
const fsGrid = `
precision highp float;
varying highp vec3 vColor;
void main(void) {
gl_FragColor = vec4(vColor, 1.0);
}
`
class MeshRenderer extends Renderer {
private readonly meshShaderProgram: WebGLProgram
private readonly gridShaderProgram: WebGLProgram
private readonly mesh: Mesh
private readonly grid: Mesh
constructor(gl: WebGLRenderingContext) {
super(gl)
this.meshShaderProgram = new ShaderProgram(gl, vsMesh, fsMesh).getProgram()
this.gridShaderProgram = new ShaderProgram(gl, vsGrid, fsGrid).getProgram()
const color: Color = [0.8, 0.8, 0.8]
this.mesh = new Mesh([
Quad.fromPoints(
new Vector(1, 0, 0),
new Vector(1, 1, 0),
new Vector(1, 1, 1),
new Vector(1, 0, 1)).setColor(color),
Quad.fromPoints(
new Vector(0, 0, 1),
new Vector(0, 1, 1),
new Vector(0, 1, 0),
new Vector(0, 0, 0)).setColor(color),
Quad.fromPoints(
new Vector(0, 1, 1),
new Vector(1, 1, 1),
new Vector(1, 1, 0),
new Vector(0, 1, 0)).setColor(color),
Quad.fromPoints(
new Vector(0, 0, 0),
new Vector(1, 0, 0),
new Vector(1, 0, 1),
new Vector(0, 0, 1)).setColor(color),
Quad.fromPoints(
new Vector(0, 0, 1),
new Vector(1, 0, 1),
new Vector(1, 1, 1),
new Vector(0, 1, 1)).setColor(color),
Quad.fromPoints(
new Vector(0, 1, 0),
new Vector(1, 1, 0),
new Vector(1, 0, 0),
new Vector(0, 0, 0)).setColor(color),
])
for (const q of this.mesh.quads) {
const normal = q.normal()
q.forEach(v => v.normal = normal)
}
this.mesh.rebuild(this.gl, { pos: true, color: true, normal: true })
this.grid = new Mesh()
this.grid.addLine(0, 0, 0, 1, 0, 0, [1, 0, 0])
this.grid.addLine(0, 0, 0, 0, 1, 0, [0, 1, 0])
this.grid.addLine(0, 0, 0, 0, 0, 1, [0, 0, 1])
this.grid.rebuild(this.gl, { pos: true, color: true })
}
public draw(view: mat4, transform: mat4) {
this.setShader(this.gridShaderProgram)
this.prepareDraw(view)
this.drawMesh(this.grid, { pos: true, color: true })
const copy = mat4.clone(view)
mat4.multiply(copy, copy, transform)
this.setShader(this.meshShaderProgram)
this.prepareDraw(copy)
this.drawMesh(this.mesh, { pos: true, color: true, normal: true })
}
}

View File

@@ -6,5 +6,6 @@ export * from './Guides.js'
export * from './Home.js'
export * from './Partners.js'
export * from './Sounds.js'
export * from './Transformation.jsx'
export * from './Versions.js'
export * from './Worldgen.jsx'

View File

@@ -87,6 +87,7 @@
"more": "More",
"move_down": "Move down",
"move_up": "Move up",
"normalize": "Normalize",
"not_found.description": "The page you were looking for does not exist.",
"no_file_chosen": "No file chosen",
"no_presets": "No presets",
@@ -123,9 +124,15 @@
"title.project": "%0% Project",
"title.new_project": "Create a new project",
"title.sounds": "Sound Explorer",
"title.transformation": "Transformation Visualizer",
"title.versions": "Versions Explorer",
"title.worldgen": "Worldgen Generators and Guides",
"tools": "Tools",
"transformation.matrix": "Matrix",
"transformation.translation": "Translation",
"transformation.left_rotation": "Left rotation",
"transformation.scale": "Scale",
"transformation.right_rotation": "Right rotation",
"trim_material": "Trim material",
"trim_pattern": "Trim pattern",
"pack_mcmeta": "Pack.mcmeta",

View File

@@ -571,6 +571,63 @@ main.has-project {
position: absolute;
}
.transformation-editor {
display: flex;
padding: 8px 16px;
flex-wrap: wrap;
gap: 16px;
}
.transformation-input {
display: flex;
align-items: center;
margin-bottom: 2px;
}
.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);
}
.transformation-title {
display: flex;
margin-bottom: 2px;
}
.transformation-title span {
margin-right: 4px;
}
.transformation-title button {
display: flex;
justify-content: center;
align-items: center;
padding: 2px 3px;
background-color: var(--background-4);
border: none;
border-radius: 3px;
margin-left: 4px;
cursor: pointer;
}
.transformation-title button:hover {
background-color: var(--background-5);
}
.transformation-section:not(:first-child) {
margin-top: 8px;
}
.transformation-matrix .transformation-input:nth-child(4n+2):not(:nth-of-type(1)) {
margin-top: 8px;
}
.btn {
display: flex;
align-items: center;