Upgrade skinview3d
This commit is contained in:
parent
950f2e6c7a
commit
5a5dbea05b
@ -48,7 +48,7 @@
|
||||
"reaptcha": "^1.7.2",
|
||||
"rxjs": "^6.5.5",
|
||||
"skinview-utils": "^0.5.5",
|
||||
"skinview3d": "^2.2.1",
|
||||
"skinview3d": "^3.0.0-alpha.1",
|
||||
"spectre.css": "^0.5.8",
|
||||
"use-immer": "^0.4.2",
|
||||
"xterm": "^4.6.0",
|
||||
|
@ -26,17 +26,12 @@ interface Props {
|
||||
initPositionZ?: number
|
||||
}
|
||||
|
||||
type AnimationHandles = {
|
||||
walk: skinview3d.SubAnimationHandle | null
|
||||
run: skinview3d.SubAnimationHandle | null
|
||||
rotate: skinview3d.SubAnimationHandle | null
|
||||
}
|
||||
|
||||
const animationHandles: AnimationHandles = {
|
||||
walk: null,
|
||||
run: null,
|
||||
rotate: null,
|
||||
}
|
||||
const animationFactories = [
|
||||
() => new skinview3d.WalkingAnimation(),
|
||||
() => new skinview3d.RunningAnimation(),
|
||||
() => new skinview3d.FlyingAnimation(),
|
||||
() => new skinview3d.IdleAnimation(),
|
||||
]
|
||||
|
||||
const ActionButton = styled.i`
|
||||
display: inline;
|
||||
@ -63,10 +58,9 @@ const Viewer: React.FC<Props> = (props) => {
|
||||
|
||||
const viewRef: React.MutableRefObject<skinview3d.SkinViewer> = useRef(null!)
|
||||
const containerRef = useRef<HTMLCanvasElement>(null)
|
||||
const animationHandlesRef = useRef(animationHandles)
|
||||
|
||||
const [paused, setPaused] = useState(false)
|
||||
const [running, setRunning] = useState(false)
|
||||
const [animation, setAnimation] = useState(0)
|
||||
const [bgPicture, setBgPicture] = useState(-1)
|
||||
|
||||
const indicator = (() => {
|
||||
@ -83,36 +77,34 @@ const Viewer: React.FC<Props> = (props) => {
|
||||
|
||||
useEffect(() => {
|
||||
const container = containerRef.current!
|
||||
const viewer = new skinview3d.FXAASkinViewer({
|
||||
const viewer = new skinview3d.SkinViewer({
|
||||
canvas: container,
|
||||
width: container.clientWidth,
|
||||
height: container.clientHeight,
|
||||
skin: props.skin || SkinSteve,
|
||||
cape: props.cape || '',
|
||||
cape: props.cape || undefined,
|
||||
model: props.isAlex ? 'slim' : 'default',
|
||||
zoom: initPositionZ / 100,
|
||||
})
|
||||
viewer.autoRotate = true
|
||||
|
||||
if (document.body.classList.contains('dark-mode')) {
|
||||
viewer.background = '#6c757d'
|
||||
}
|
||||
|
||||
const rotate = viewer.animations.add(skinview3d.RotatingAnimation)
|
||||
animationHandlesRef.current.rotate = rotate
|
||||
|
||||
const control = skinview3d.createOrbitControls(viewer)
|
||||
|
||||
viewRef.current = viewer
|
||||
|
||||
return () => {
|
||||
control.dispose()
|
||||
viewer.dispose()
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const viewer = viewRef.current
|
||||
viewer.loadSkin(props.skin || SkinSteve, props.isAlex ? 'slim' : 'default')
|
||||
viewer.loadSkin(props.skin || SkinSteve, {
|
||||
model: props.isAlex ? 'slim' : 'default',
|
||||
})
|
||||
}, [props.skin, props.isAlex])
|
||||
|
||||
useEffect(() => {
|
||||
@ -125,52 +117,63 @@ const Viewer: React.FC<Props> = (props) => {
|
||||
}, [props.cape])
|
||||
|
||||
useEffect(() => {
|
||||
const handles = animationHandlesRef.current
|
||||
if (running) {
|
||||
handles.walk?.resetAndRemove()
|
||||
handles.walk = null
|
||||
|
||||
const run = viewRef.current.animations.add(skinview3d.RunningAnimation)
|
||||
run.speed = 0.6
|
||||
handles.run = run
|
||||
const viewer = viewRef.current
|
||||
const factory = animationFactories[animation]
|
||||
if (factory === undefined) {
|
||||
viewer.animation = null
|
||||
} else {
|
||||
handles.run?.resetAndRemove()
|
||||
handles.run = null
|
||||
|
||||
const walk = viewRef.current.animations.add(skinview3d.WalkingAnimation)
|
||||
handles.walk = walk
|
||||
const newAnimation = factory()
|
||||
newAnimation.paused = paused // Perseve `paused` state
|
||||
viewer.animation = newAnimation
|
||||
}
|
||||
}, [running])
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [animation])
|
||||
|
||||
useEffect(() => {
|
||||
viewRef.current.animations.paused = paused
|
||||
const currentAnimation = viewRef.current.animation
|
||||
if (currentAnimation !== null) {
|
||||
currentAnimation.paused = paused
|
||||
}
|
||||
}, [paused])
|
||||
|
||||
useEffect(() => {
|
||||
viewRef.current.loadBackground(backgrounds[bgPicture]!)
|
||||
const viewer = viewRef.current
|
||||
const backgroundUrl = backgrounds[bgPicture]
|
||||
if (backgroundUrl === undefined) {
|
||||
viewer.background = null
|
||||
} else {
|
||||
viewer.loadBackground(backgroundUrl)
|
||||
}
|
||||
}, [bgPicture])
|
||||
|
||||
const togglePause = () => {
|
||||
setPaused((paused) => !paused)
|
||||
setPaused((paused) => {
|
||||
if (paused) {
|
||||
return false
|
||||
} else {
|
||||
viewRef.current.autoRotate = false
|
||||
return true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const toggleRun = () => {
|
||||
setRunning((running) => !running)
|
||||
const toggleAnimation = () => {
|
||||
setAnimation((index) => (index + 1) % animationFactories.length)
|
||||
setPaused(false)
|
||||
}
|
||||
|
||||
const toggleRotate = () => {
|
||||
const handles = animationHandlesRef.current
|
||||
if (handles.rotate) {
|
||||
handles.rotate.paused = !handles.rotate.paused
|
||||
}
|
||||
const viewer = viewRef.current
|
||||
viewer.autoRotate = !viewer.autoRotate
|
||||
}
|
||||
|
||||
const handleReset = () => {
|
||||
const handles = animationHandlesRef.current
|
||||
handles.walk?.resetAndRemove()
|
||||
handles.run?.resetAndRemove()
|
||||
handles.rotate?.resetAndRemove()
|
||||
viewRef.current.animations.paused = true
|
||||
const toggleBackEquippment = () => {
|
||||
const player = viewRef.current.playerObject
|
||||
if (player.backEquipment === 'cape') {
|
||||
player.backEquipment = 'elytra'
|
||||
} else {
|
||||
player.backEquipment = 'cape'
|
||||
}
|
||||
}
|
||||
|
||||
const setWhite = () => {
|
||||
@ -213,32 +216,36 @@ const Viewer: React.FC<Props> = (props) => {
|
||||
</h3>
|
||||
<div>
|
||||
<ActionButton
|
||||
className={`fas fa-${running ? 'walking' : 'running'}`}
|
||||
className={`fas fa-tablet ${props.cape ? '' : 'd-none'}`}
|
||||
data-toggle="tooltip"
|
||||
data-placement="bottom"
|
||||
title={`${t('general.walk')} / ${t('general.run')}`}
|
||||
onClick={toggleRun}
|
||||
title={t('general.switchCapeElytra')}
|
||||
onClick={toggleBackEquippment}
|
||||
></ActionButton>
|
||||
<ActionButton
|
||||
className="fas fa-redo-alt"
|
||||
className={`fas fa-person-running`}
|
||||
data-toggle="tooltip"
|
||||
data-placement="bottom"
|
||||
title={t('general.rotation')}
|
||||
onClick={toggleRotate}
|
||||
title={t('general.switchAnimation')}
|
||||
onClick={toggleAnimation}
|
||||
></ActionButton>
|
||||
<ActionButton
|
||||
className={`fas fa-${paused ? 'play' : 'pause'}`}
|
||||
data-toggle="tooltip"
|
||||
data-placement="bottom"
|
||||
title={t('general.pause')}
|
||||
title={
|
||||
paused
|
||||
? t('general.playAnimation')
|
||||
: t('general.pauseAnimation')
|
||||
}
|
||||
onClick={togglePause}
|
||||
></ActionButton>
|
||||
<ActionButton
|
||||
className="fas fa-stop"
|
||||
className="fas fa-rotate-right"
|
||||
data-toggle="tooltip"
|
||||
data-placement="bottom"
|
||||
title={t('general.reset')}
|
||||
onClick={handleReset}
|
||||
title={t('general.rotation')}
|
||||
onClick={toggleRotate}
|
||||
></ActionButton>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,24 +1,32 @@
|
||||
/* eslint-disable max-params */
|
||||
/* eslint-disable max-classes-per-file */
|
||||
import type { PlayerObject, SkinObject, CapeObject } from 'skinview3d'
|
||||
import type {
|
||||
PlayerObject,
|
||||
SkinObject,
|
||||
CapeObject,
|
||||
EarsObject,
|
||||
} from 'skinview3d'
|
||||
|
||||
export class FXAASkinViewer {
|
||||
export class SkinViewer {
|
||||
disposed = false
|
||||
background = ''
|
||||
animations = new RootAnimation()
|
||||
animationPaused = false
|
||||
background = null
|
||||
animation = null
|
||||
autoRotate = false
|
||||
autoRotateSpeed = 1.0
|
||||
|
||||
playerObject: PlayerObject
|
||||
|
||||
constructor() {
|
||||
this.animationPaused = false
|
||||
this.playerObject = {
|
||||
skin: {} as SkinObject,
|
||||
cape: {} as CapeObject,
|
||||
ears: {} as EarsObject,
|
||||
backEquipment: 'cape',
|
||||
} as PlayerObject
|
||||
}
|
||||
|
||||
loadSkin() {}
|
||||
resetSkin() {}
|
||||
loadCape() {}
|
||||
resetCape() {}
|
||||
loadBackground() {}
|
||||
@ -28,44 +36,19 @@ export class FXAASkinViewer {
|
||||
}
|
||||
}
|
||||
|
||||
export class RootAnimation {
|
||||
export class PlayerAnimation {
|
||||
speed = 1.0
|
||||
paused = false
|
||||
|
||||
add(animation: unknown) {
|
||||
return animation
|
||||
}
|
||||
progress = 0
|
||||
}
|
||||
|
||||
export function createOrbitControls() {
|
||||
return {
|
||||
dispose() {},
|
||||
}
|
||||
}
|
||||
export class IdleAnimation extends PlayerAnimation {}
|
||||
|
||||
export const WalkingAnimation = new Proxy(
|
||||
{},
|
||||
{
|
||||
get() {
|
||||
return jest.fn()
|
||||
},
|
||||
},
|
||||
)
|
||||
export const RunningAnimation = new Proxy(
|
||||
{},
|
||||
{
|
||||
get() {
|
||||
return jest.fn()
|
||||
},
|
||||
},
|
||||
)
|
||||
export const RotatingAnimation = new Proxy(
|
||||
{},
|
||||
{
|
||||
get() {
|
||||
return jest.fn()
|
||||
},
|
||||
},
|
||||
)
|
||||
export class WalkingAnimation extends PlayerAnimation {}
|
||||
|
||||
export class RunningAnimation extends PlayerAnimation {}
|
||||
|
||||
export class FlyingAnimation extends PlayerAnimation {}
|
||||
|
||||
export function isSlimSkin() {
|
||||
return false
|
||||
|
@ -49,33 +49,27 @@ describe('indicator', () => {
|
||||
})
|
||||
|
||||
describe('actions', () => {
|
||||
it('toggle run', () => {
|
||||
const { getByTitle } = render(<Viewer isAlex={false} />)
|
||||
fireEvent.click(getByTitle(`${t('general.walk')} / ${t('general.run')}`))
|
||||
it('toggle animation', () => {
|
||||
const component = <Viewer isAlex={false} />
|
||||
const { getByTitle } = render(component)
|
||||
fireEvent.click(getByTitle(`${t('general.switchAnimation')}`)) // should start running
|
||||
fireEvent.click(getByTitle(`${t('general.switchAnimation')}`)) // should start flying
|
||||
fireEvent.click(getByTitle(`${t('general.switchAnimation')}`)) // should be idle
|
||||
fireEvent.click(getByTitle(`${t('general.switchAnimation')}`)) // should start walking
|
||||
})
|
||||
|
||||
it('toggle rotation', () => {
|
||||
const { getByTitle } = render(<Viewer isAlex={false} />)
|
||||
fireEvent.click(getByTitle(t('general.rotation')))
|
||||
fireEvent.click(getByTitle(t('general.rotation'))) // should stop rotation
|
||||
fireEvent.click(getByTitle(t('general.rotation'))) // should start rotation
|
||||
})
|
||||
|
||||
it('toggle pause', () => {
|
||||
const { getByTitle } = render(<Viewer isAlex={false} />)
|
||||
const icon = getByTitle(t('general.pause'))
|
||||
const icon = getByTitle(t('general.pauseAnimation'))
|
||||
fireEvent.click(icon)
|
||||
expect(icon).toHaveClass('fa-play')
|
||||
})
|
||||
|
||||
it('reset', () => {
|
||||
const { getByTitle } = render(<Viewer isAlex={false} />)
|
||||
fireEvent.click(getByTitle(t('general.reset')))
|
||||
})
|
||||
|
||||
it('reset when running', () => {
|
||||
const { getByTitle } = render(<Viewer isAlex={false} />)
|
||||
fireEvent.click(getByTitle(`${t('general.walk')} / ${t('general.run')}`))
|
||||
fireEvent.click(getByTitle(t('general.reset')))
|
||||
})
|
||||
})
|
||||
|
||||
describe('background', () => {
|
||||
|
@ -62,7 +62,7 @@ skinlib:
|
||||
setPublicNotice: Sure to set this as public texture?
|
||||
setPrivateNotice: Sure to set this as private texture?
|
||||
deleteNotice: Are you sure to delete this texture?
|
||||
setNewTextureModel: "Please select a new texture model:"
|
||||
setNewTextureModel: 'Please select a new texture model:'
|
||||
upload:
|
||||
texture-name: Texture Name
|
||||
texture-type: Texture Type
|
||||
@ -248,11 +248,11 @@ general:
|
||||
tip: Tip
|
||||
noResult: No result.
|
||||
texturePreview: Texture Preview
|
||||
walk: Walk
|
||||
run: Run
|
||||
rotation: Rotation
|
||||
pause: Pause
|
||||
reset: Reset
|
||||
rotation: Toggle rotation
|
||||
playAnimation: Play animation
|
||||
pauseAnimation: Pause animation
|
||||
switchAnimation: Switch animation
|
||||
switchCapeElytra: Switch Cape / Elytra
|
||||
skinlib: Skin Library
|
||||
wait: Please wait...
|
||||
csrf: This page is out-dated. Please refresh it.
|
||||
|
@ -62,7 +62,7 @@ skinlib:
|
||||
setPublicNotice: 要将此材质设置为公开吗?
|
||||
setPrivateNotice: 要将此材质设置为私有吗?
|
||||
deleteNotice: 真的要删除此材质吗?
|
||||
setNewTextureModel: "请选择新的材质适用模型:"
|
||||
setNewTextureModel: '请选择新的材质适用模型:'
|
||||
upload:
|
||||
texture-name: 材质名称
|
||||
texture-type: 材质类型
|
||||
@ -242,11 +242,11 @@ general:
|
||||
tip: 提示
|
||||
noResult: 无结果
|
||||
texturePreview: 材质预览
|
||||
walk: 行走
|
||||
run: 奔跑
|
||||
rotation: 旋转
|
||||
pause: 暂停
|
||||
reset: 重置
|
||||
playAnimation: 播放动画
|
||||
pauseAnimation: 暂停动画
|
||||
switchAnimation: 切换动画
|
||||
switchCapeElytra: 切换披风 / 鞘翅
|
||||
skinlib: 皮肤库
|
||||
wait: 请稍等...
|
||||
csrf: 页面已过期,请刷新页面。
|
||||
|
45
yarn.lock
45
yarn.lock
@ -1744,10 +1744,12 @@
|
||||
dependencies:
|
||||
"@types/jest" "*"
|
||||
|
||||
"@types/three@^0.136.1":
|
||||
version "0.136.1"
|
||||
resolved "https://registry.npmjs.org/@types/three/-/three-0.136.1.tgz#030fc01cdc5ce82cad93db17b94a24515cb4b50e"
|
||||
integrity sha512-gzTw6RR4dU8sGf+RpLBWWKHRVIJ4gwKVQPk+IFCgha04Efg/itXYUalX6iBcYeSmaDu0qE5M7uTq0XLQpU4FAg==
|
||||
"@types/three@^0.142.0":
|
||||
version "0.142.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/three/-/three-0.142.0.tgz#32ca897afc925057174a398416777fcb141666d6"
|
||||
integrity sha512-YNVnlqthfgqsxKuSNbT/G2r+ha/6x+aJm7kvjT+sxa4jUROw/KOPm5xzGj33sk1+Librg6NwH/wJ6LCDX33UZg==
|
||||
dependencies:
|
||||
"@types/webxr" "*"
|
||||
|
||||
"@types/tween.js@^18.5.0":
|
||||
version "18.5.0"
|
||||
@ -1793,6 +1795,11 @@
|
||||
"@types/webpack-sources" "*"
|
||||
source-map "^0.6.0"
|
||||
|
||||
"@types/webxr@*":
|
||||
version "0.5.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/webxr/-/webxr-0.5.1.tgz#4908349419104bd476a4252d04e4c3abb496748d"
|
||||
integrity sha512-xlFXPfgJR5vIuDefhaHuUM9uUgvPaXB6GKdXy2gdEh8gBWQZ2ul24AJz3foUd8NNKlSTQuWYJpCb1/pL81m1KQ==
|
||||
|
||||
"@types/yargs-parser@*":
|
||||
version "13.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-13.1.0.tgz#c563aa192f39350a1d18da36c5a8da382bbd8228"
|
||||
@ -8815,21 +8822,21 @@ skinview-utils@^0.5.5:
|
||||
resolved "https://registry.npmjs.org/skinview-utils/-/skinview-utils-0.5.5.tgz#55671db8db97f82df0f87e1c8ec95bd58e861bc8"
|
||||
integrity sha512-lApkcJuGQhK9j4oGeaPTjAx/WiRMduunho7RUoBbP+ceOCbncHogQNkfwbil7pjgvyy8S82pBdfzOQggjbZMdg==
|
||||
|
||||
skinview-utils@^0.6.2:
|
||||
version "0.6.2"
|
||||
resolved "https://registry.npmjs.org/skinview-utils/-/skinview-utils-0.6.2.tgz#1247aef71c472e963fd3f0db3494f18d63e31e51"
|
||||
integrity sha512-UdjWwXCVZobtG+dc7ilvMRbtXYSqPJtWKPFdgWc44Gs4aoOmZML2lErr77h7uussXo9zrcR+fPizGshGpdETvQ==
|
||||
skinview-utils@^0.7.0:
|
||||
version "0.7.0"
|
||||
resolved "https://registry.yarnpkg.com/skinview-utils/-/skinview-utils-0.7.0.tgz#84164d06ff4666c731288ebb7783b7be964bda0c"
|
||||
integrity sha512-ecbbUp0AuvZX5fCIOOwbNPxr/JIbrAGhCcdLcww0Ov9PbvAbeYjNDSIu1ebCJBe4CWc6ZYYn8MFNp68DDta0iQ==
|
||||
dependencies:
|
||||
"@types/offscreencanvas" "^2019.6.4"
|
||||
|
||||
skinview3d@^2.2.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.npmjs.org/skinview3d/-/skinview3d-2.2.1.tgz#f66f29a35055d378a5949c72512e2c6eddaad02a"
|
||||
integrity sha512-VOzB2jcsXWTmyEqE6nIXhECg1+29ZcZjP0tw9mFIGN9Y4P/hDlAt2ev7NT7J0Sqgei5UJenkorW24VDZHCepKw==
|
||||
skinview3d@^3.0.0-alpha.1:
|
||||
version "3.0.0-alpha.1"
|
||||
resolved "https://registry.yarnpkg.com/skinview3d/-/skinview3d-3.0.0-alpha.1.tgz#48f43243a96ddebba9b3657e1ebcc5bd45e20638"
|
||||
integrity sha512-Y5v5yxQIVZZwpQlnRHeVZ9pgDOETLkPrkeMcwJN92OFAYQ0+WTHy6WlZOu0M1DRys+h8iyBdxRb/9976ftt72Q==
|
||||
dependencies:
|
||||
"@types/three" "^0.136.1"
|
||||
skinview-utils "^0.6.2"
|
||||
three "^0.136.0"
|
||||
"@types/three" "^0.142.0"
|
||||
skinview-utils "^0.7.0"
|
||||
three "^0.142.0"
|
||||
|
||||
slash@^2.0.0:
|
||||
version "2.0.0"
|
||||
@ -9404,10 +9411,10 @@ text-table@^0.2.0:
|
||||
resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
|
||||
integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=
|
||||
|
||||
three@^0.136.0:
|
||||
version "0.136.0"
|
||||
resolved "https://registry.npmjs.org/three/-/three-0.136.0.tgz#b1504db021b46398ef468aa7849f3dcabb814f50"
|
||||
integrity sha512-+fEMX7nYLz2ZesVP/dyifli5Jf8gR3XPAnFJveQ80aMhibFduzrADnjMbARXh8+W9qLK7rshJCjAIL/6cDxC+A==
|
||||
three@^0.142.0:
|
||||
version "0.142.0"
|
||||
resolved "https://registry.yarnpkg.com/three/-/three-0.142.0.tgz#89e226a16221f212eb1d40f0786604b711f28aed"
|
||||
integrity sha512-ESjPO+3geFr+ZUfVMpMnF/eVU2uJPOh0e2ZpMFqjNca1wApS9lJb7E4MjwGIczgt9iuKd8PEm6Pfgp2bJ92Xtg==
|
||||
|
||||
throat@^6.0.1:
|
||||
version "6.0.1"
|
||||
|
Loading…
Reference in New Issue
Block a user