diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 9ab6a31864..cc52a1b293 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -3570,6 +3570,12 @@ _miWorld: antialiasing: "アンチエイリアス" avatar: "アバター" + _avatars: + _default: + body: "ボディ" + eyes: "目" + mouth: "口" + _miRoom: snapToGrid: "グリッドにスナップ" gridScale: "グリッドサイズ" diff --git a/packages/backend/src/core/entities/WorldAvatarEntityService.ts b/packages/backend/src/core/entities/WorldAvatarEntityService.ts index cf33d07563..3fd817b6aa 100644 --- a/packages/backend/src/core/entities/WorldAvatarEntityService.ts +++ b/packages/backend/src/core/entities/WorldAvatarEntityService.ts @@ -78,5 +78,16 @@ export class WorldAvatarEntityService { .then(users => new Map(users.map(u => [u.id, u]))); return Promise.all(avatars.map(avatar => this.packLite(avatar, me, { packedUser: _userMap.get(avatar.userId) }))); } + + @bindThis + public async packDetailedMany( + avatars: MiWorldAvatar[], + me?: { id: MiUser['id'] } | null | undefined, + ) { + const _users = avatars.map(({ user, userId }) => user ?? userId); + const _userMap = await this.userEntityService.packMany(_users, me) + .then(users => new Map(users.map(u => [u.id, u]))); + return Promise.all(avatars.map(avatar => this.packDetailed(avatar, me, { packedUser: _userMap.get(avatar.userId) }))); + } } diff --git a/packages/backend/src/server/api/endpoints/world/avatars/list.ts b/packages/backend/src/server/api/endpoints/world/avatars/list.ts index 91b0e4ae68..ea27f638f8 100644 --- a/packages/backend/src/server/api/endpoints/world/avatars/list.ts +++ b/packages/backend/src/server/api/endpoints/world/avatars/list.ts @@ -24,7 +24,7 @@ export const meta = { items: { type: 'object', optional: false, nullable: false, - ref: 'WorldAvatarLite', + ref: 'WorldAvatarDetailed', }, }, @@ -41,7 +41,7 @@ export const paramDef = { sinceDate: { type: 'integer' }, untilDate: { type: 'integer' }, }, - required: ['userId'], + required: [], } as const; @Injectable() @@ -55,8 +55,8 @@ export default class extends Endpoint { // eslint- const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null); const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.gen(ps.sinceDate!) : null); - const avatars = await this.worldAvatarService.getMyAvatarsWithPagination(ps.userId, ps.limit, sinceId, untilId); - return this.worldAvatarEntityService.packLiteMany(avatars, me); + const avatars = await this.worldAvatarService.getMyAvatarsWithPagination(me.id, ps.limit, sinceId, untilId); + return this.worldAvatarEntityService.packDetailedMany(avatars, me); }); } } diff --git a/packages/frontend-misskey-world-engine/src/PlayerContainer.ts b/packages/frontend-misskey-world-engine/src/PlayerContainer.ts index 5efde2aad9..bbafef2603 100644 --- a/packages/frontend-misskey-world-engine/src/PlayerContainer.ts +++ b/packages/frontend-misskey-world-engine/src/PlayerContainer.ts @@ -20,6 +20,27 @@ export type PlayerState = { sit?: string; // id }; +const DEFAULT_FACE_PARTS_EYES = { + '_none_': null, + 'a': '/client-assets/world/avatars/eyes-a.png', + 'b': '/client-assets/world/avatars/eyes-b.png', + 'c': '/client-assets/world/avatars/eyes-c.png', + 'd': '/client-assets/world/avatars/eyes-d.png', + 'e': '/client-assets/world/avatars/eyes-e.png', + 'f': '/client-assets/world/avatars/eyes-f.png', + 'g': '/client-assets/world/avatars/eyes-g.png', +}; + +const DEFAULT_FACE_PARTS_MOUTH = { + '_none_': null, + 'a': '/client-assets/world/avatars/mouth-a.png', + 'b': '/client-assets/world/avatars/mouth-b.png', + 'c': '/client-assets/world/avatars/mouth-c.png', + 'd': '/client-assets/world/avatars/mouth-d.png', + 'e': '/client-assets/world/avatars/mouth-e.png', + 'f': '/client-assets/world/avatars/mouth-f.png', +}; + export class PlayerContainer { public id: string; private profile: PlayerProfile; @@ -67,9 +88,27 @@ export class PlayerContainer { const avatarTex = new BABYLON.Texture(this.profile.avatarUrl, this.scene, false, false); + let eyesTex: BABYLON.Texture | null = null; + if (this.profile.worldAvatar.eyes.type in DEFAULT_FACE_PARTS_EYES) { + const eyesTexPath = DEFAULT_FACE_PARTS_EYES[this.profile.worldAvatar.eyes.type]; + if (eyesTexPath) { + eyesTex = new BABYLON.Texture(eyesTexPath, this.scene, false, false); + eyesTex.hasAlpha = true; + } + } + + let mouthTex: BABYLON.Texture | null = null; + if (this.profile.worldAvatar.mouth.type in DEFAULT_FACE_PARTS_MOUTH) { + const mouthTexPath = DEFAULT_FACE_PARTS_MOUTH[this.profile.worldAvatar.mouth.type]; + if (mouthTexPath) { + mouthTex = new BABYLON.Texture(mouthTexPath, this.scene, false, false); + mouthTex.hasAlpha = true; + } + } + for (const mesh of this.modelRoot.getChildMeshes()) { if (mesh.name.includes('__AVATAR__')) { - const mat = new BABYLON.PBRMaterial(`${mesh.name}-mat`, this.scene); + const mat = new BABYLON.PBRMaterial('', this.scene); mat.albedoColor = new BABYLON.Color3(0.5, 0.5, 0.5); mat.albedoTexture = avatarTex; mat.emissiveColor = new BABYLON.Color3(0.5, 0.5, 0.5); @@ -82,6 +121,26 @@ export class PlayerContainer { if (mesh.name.includes('__BODY__')) { mesh.material.albedoColor = new BABYLON.Color3(this.profile.worldAvatar.body.color[0], this.profile.worldAvatar.body.color[1], this.profile.worldAvatar.body.color[2]); } + if (mesh.name.includes('__EYES__')) { + const mat = new BABYLON.PBRMaterial('', this.scene); + mat.albedoColor = new BABYLON.Color3(this.profile.worldAvatar.eyes.color[0], this.profile.worldAvatar.eyes.color[1], this.profile.worldAvatar.eyes.color[2]); + mat.albedoTexture = eyesTex; + mat.roughness = 1; + mat.metallic = 0; + mesh.material = mat; + } + if (mesh.name.includes('__MOUTH__')) { + if (mouthTex != null) { + const mat = new BABYLON.PBRMaterial('', this.scene); + mat.albedoColor = new BABYLON.Color3(this.profile.worldAvatar.mouth.color[0], this.profile.worldAvatar.mouth.color[1], this.profile.worldAvatar.mouth.color[2]); + mat.albedoTexture = mouthTex; + mat.roughness = 1; + mat.metallic = 0; + mesh.material = mat; + } else { + mesh.isVisible = false; + } + } } this.registerMeshes(this.modelRoot.getChildMeshes()); @@ -94,9 +153,6 @@ export class PlayerContainer { { frame: 90, value: cm(2) }, { frame: 120, value: cm(0) }, ]); - //const easing = new BABYLON.CubicEase(); - //easing.setEasingMode(BABYLON.EasingFunction.EASINGMODE_EASEINOUT); - //anim.setEasingFunction(easing); this.modelRootContainerForAnim.animations = [anim]; this.animationObserver = this.scene.onAfterAnimationsObservable.add(() => { this.sr.updateMesh(this.modelRootContainerForAnim.getChildMeshes(), false); diff --git a/packages/frontend-misskey-world-engine/src/avatarPreviewEngine.ts b/packages/frontend-misskey-world-engine/src/avatarPreviewEngine.ts index 49a348d68a..b436c82d96 100644 --- a/packages/frontend-misskey-world-engine/src/avatarPreviewEngine.ts +++ b/packages/frontend-misskey-world-engine/src/avatarPreviewEngine.ts @@ -44,7 +44,7 @@ export class AvatarPreviewEngine extends EngineBase<{ // PlayerPreviewEngineに this.scene.autoClear = false; this.scene.skipPointerMovePicking = true; this.scene.skipFrustumClipping = true; // snapshot renderingでは全てのメッシュがアクティブになっている必要があるため - this.scene.clearColor = new BABYLON.Color4(0.03, 0.03, 0.03, 1); + this.scene.clearColor = new BABYLON.Color4(0.01, 0.01, 0.01, 1); this.sr = new BABYLON.SnapshotRenderingHelper(this.scene); @@ -160,8 +160,6 @@ export class AvatarPreviewEngine extends EngineBase<{ // PlayerPreviewEngineに this.camera.fov = 0.5; this.camera.lowerRadiusLimit = cm(50); this.camera.upperRadiusLimit = cm(1000); - this.camera.useAutoRotationBehavior = true; - this.camera.autoRotationBehavior!.idleRotationSpeed = 0.3; //this.camera.mode = BABYLON.Camera.ORTHOGRAPHIC_CAMERA; this.camera.setTarget(new BABYLON.Vector3(0, boundingInfo.centerWorld.y, 0)); this.camera.inputs.clear(); diff --git a/packages/frontend/assets/world/avatars/default.blend b/packages/frontend/assets/world/avatars/default.blend index c6dd5024e1..74ffb0d8f2 100644 Binary files a/packages/frontend/assets/world/avatars/default.blend and b/packages/frontend/assets/world/avatars/default.blend differ diff --git a/packages/frontend/assets/world/avatars/default.glb b/packages/frontend/assets/world/avatars/default.glb index 028b1897b0..ba3119bf46 100644 Binary files a/packages/frontend/assets/world/avatars/default.glb and b/packages/frontend/assets/world/avatars/default.glb differ diff --git a/packages/frontend/assets/world/avatars/eyes-a.png b/packages/frontend/assets/world/avatars/eyes-a.png new file mode 100644 index 0000000000..4395aff516 Binary files /dev/null and b/packages/frontend/assets/world/avatars/eyes-a.png differ diff --git a/packages/frontend/assets/world/avatars/eyes-b.png b/packages/frontend/assets/world/avatars/eyes-b.png new file mode 100644 index 0000000000..335d4de5e8 Binary files /dev/null and b/packages/frontend/assets/world/avatars/eyes-b.png differ diff --git a/packages/frontend/assets/world/avatars/eyes-c.png b/packages/frontend/assets/world/avatars/eyes-c.png new file mode 100644 index 0000000000..54c1e14ddb Binary files /dev/null and b/packages/frontend/assets/world/avatars/eyes-c.png differ diff --git a/packages/frontend/assets/world/avatars/eyes-d.png b/packages/frontend/assets/world/avatars/eyes-d.png new file mode 100644 index 0000000000..fb07e83ba5 Binary files /dev/null and b/packages/frontend/assets/world/avatars/eyes-d.png differ diff --git a/packages/frontend/assets/world/avatars/eyes-e.png b/packages/frontend/assets/world/avatars/eyes-e.png new file mode 100644 index 0000000000..45c3f86178 Binary files /dev/null and b/packages/frontend/assets/world/avatars/eyes-e.png differ diff --git a/packages/frontend/assets/world/avatars/eyes-f.png b/packages/frontend/assets/world/avatars/eyes-f.png new file mode 100644 index 0000000000..13f7fab264 Binary files /dev/null and b/packages/frontend/assets/world/avatars/eyes-f.png differ diff --git a/packages/frontend/assets/world/avatars/eyes-g.png b/packages/frontend/assets/world/avatars/eyes-g.png new file mode 100644 index 0000000000..bb3a9f7d29 Binary files /dev/null and b/packages/frontend/assets/world/avatars/eyes-g.png differ diff --git a/packages/frontend/assets/world/avatars/face-parts.af b/packages/frontend/assets/world/avatars/face-parts.af new file mode 100644 index 0000000000..5b2908db14 Binary files /dev/null and b/packages/frontend/assets/world/avatars/face-parts.af differ diff --git a/packages/frontend/assets/world/avatars/mouth-a.png b/packages/frontend/assets/world/avatars/mouth-a.png new file mode 100644 index 0000000000..b2135196ad Binary files /dev/null and b/packages/frontend/assets/world/avatars/mouth-a.png differ diff --git a/packages/frontend/assets/world/avatars/mouth-b.png b/packages/frontend/assets/world/avatars/mouth-b.png new file mode 100644 index 0000000000..67751b328e Binary files /dev/null and b/packages/frontend/assets/world/avatars/mouth-b.png differ diff --git a/packages/frontend/assets/world/avatars/mouth-c.png b/packages/frontend/assets/world/avatars/mouth-c.png new file mode 100644 index 0000000000..e594edaa82 Binary files /dev/null and b/packages/frontend/assets/world/avatars/mouth-c.png differ diff --git a/packages/frontend/assets/world/avatars/mouth-d.png b/packages/frontend/assets/world/avatars/mouth-d.png new file mode 100644 index 0000000000..242d107120 Binary files /dev/null and b/packages/frontend/assets/world/avatars/mouth-d.png differ diff --git a/packages/frontend/assets/world/avatars/mouth-e.png b/packages/frontend/assets/world/avatars/mouth-e.png new file mode 100644 index 0000000000..1bd6b0add0 Binary files /dev/null and b/packages/frontend/assets/world/avatars/mouth-e.png differ diff --git a/packages/frontend/assets/world/avatars/mouth-f.png b/packages/frontend/assets/world/avatars/mouth-f.png new file mode 100644 index 0000000000..64757a3d88 Binary files /dev/null and b/packages/frontend/assets/world/avatars/mouth-f.png differ diff --git a/packages/frontend/src/pages/rooms/edit-world-avatar-dialog.vue b/packages/frontend/src/components/MkWorldAvatarEditDialog.vue similarity index 69% rename from packages/frontend/src/pages/rooms/edit-world-avatar-dialog.vue rename to packages/frontend/src/components/MkWorldAvatarEditDialog.vue index 1342c066e8..9fbfaa572b 100644 --- a/packages/frontend/src/pages/rooms/edit-world-avatar-dialog.vue +++ b/packages/frontend/src/components/MkWorldAvatarEditDialog.vue @@ -9,7 +9,8 @@ SPDX-License-Identifier: AGPL-3.0-only :width="1000" :height="600" :scroll="false" - :withOkButton="false" + :withOkButton="true" + @ok="ok" @close="cancel()" @closed="emit('closed')" > @@ -35,9 +36,63 @@ SPDX-License-Identifier: AGPL-3.0-only :leaveToClass="prefer.s.animation ? $style.transition_options_leaveTo : ''" >
- - - +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
@@ -49,11 +104,9 @@ SPDX-License-Identifier: AGPL-3.0-only + + diff --git a/packages/frontend/src/pages/settings/preferences.vue b/packages/frontend/src/pages/settings/preferences.vue index 49094faa2f..5bd356211c 100644 --- a/packages/frontend/src/pages/settings/preferences.vue +++ b/packages/frontend/src/pages/settings/preferences.vue @@ -829,11 +829,7 @@ SPDX-License-Identifier: AGPL-3.0-only -
-
- -
-
+ @@ -970,6 +966,7 @@ import MkDisableSection from '@/components/MkDisableSection.vue'; import FormLink from '@/components/form/link.vue'; import MkLink from '@/components/MkLink.vue'; import MkInfo from '@/components/MkInfo.vue'; +import MkWorldAvatarManager from '@/components/MkWorldAvatarManager.vue'; import { store } from '@/store.js'; import * as os from '@/os.js'; import { misskeyApi } from '@/utility/misskey-api.js'; @@ -1233,20 +1230,6 @@ function testNotification(): void { }, 300); } -async function createWorldAvatar(ev: PointerEvent) { - const { dispose } = await os.popupAsyncWithDialog(import('../rooms/edit-world-avatar-dialog.vue').then(x => x.default), { - graphicsQuality: worldGraphicsQuality.value ?? 0, - avatar: null, - }, { - ok: async (res) => { - console.log(res); - }, - closed: () => { - dispose(); - }, - }); -} - const headerActions = computed(() => []); const headerTabs = computed(() => []); diff --git a/packages/i18n/src/autogen/locale.ts b/packages/i18n/src/autogen/locale.ts index dde98319d1..f430a1afa9 100644 --- a/packages/i18n/src/autogen/locale.ts +++ b/packages/i18n/src/autogen/locale.ts @@ -13319,6 +13319,22 @@ export interface Locale extends ILocale { * アバター */ "avatar": string; + "_avatars": { + "_default": { + /** + * ボディ + */ + "body": string; + /** + * 目 + */ + "eyes": string; + /** + * 口 + */ + "mouth": string; + }; + }; }; "_miRoom": { /** diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts index 4156ef6ff7..33f1134af5 100644 --- a/packages/misskey-js/src/autogen/types.ts +++ b/packages/misskey-js/src/autogen/types.ts @@ -37246,7 +37246,7 @@ export interface operations { [name: string]: unknown; }; content: { - 'application/json': components['schemas']['WorldAvatarLite'][]; + 'application/json': components['schemas']['WorldAvatarDetailed'][]; }; }; /** @description Client error */