mirror of
https://github.com/misode/misode.github.io.git
synced 2026-04-24 23:56:51 +00:00
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:
@@ -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 />
|
||||
|
||||
178
src/app/Utils.ts
178
src/app/Utils.ts
@@ -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
|
||||
}
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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" />
|
||||
|
||||
288
src/app/pages/Transformation.tsx
Normal file
288
src/app/pages/Transformation.tsx
Normal 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 })
|
||||
}
|
||||
}
|
||||
@@ -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'
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user