diff --git a/.gitattributes b/.gitattributes
index 952d6cd0e9df1e13528d2ea34a59c92582ab37d3..a175917f31ca16f29edb1bd3fa5e26c0ed1b099b 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,3 +1,7 @@
 *.svg -diff -text
 *.psd -diff -text
 *.ai -diff -text
+*.mqo -diff -text
+*.glb -diff -text
+*.blend -diff -text
+*.afdesign -diff -text
diff --git a/.gitignore b/.gitignore
index e438ad890cf08bfa71c25494e63eef15a41726d7..4baefc147fd6ed429e23be869d591d8a08c1f6aa 100644
--- a/.gitignore
+++ b/.gitignore
@@ -30,3 +30,10 @@ api-docs.json
 .DS_Store
 /files
 ormconfig.json
+
+# blender backups
+*.blend1
+*.blend2
+*.blend3
+*.blend4
+*.blend5
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index ba45f5d23c0b701ee568a6a9a28f9fdf4d0a03d7..0c3bfa42f75c63f3d78e6d784f8711fe3c9f80b0 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -38,6 +38,16 @@ Documentation of Vue I18n is available at http://kazupon.github.io/vue-i18n/intr
 Misskey uses CircleCI for executing automated tests.
 Configuration files are located in [`/.circleci`](/.circleci).
 
+## Adding MisskeyRoom items
+Currently, we accept only 3D models created with [Blender](https://www.blender.org/).
+
+* Use English for material, object and texture names
+* Use meter for unit of length
+* Your PR must include all source files of your models (for later editing)
+* Your PR must include the glTF binary files (.glb) of your models
+
+You can find information on glTF 2.0 at [glTF 2.0 — Blender Manual]( https://docs.blender.org/manual/en/dev/addons/io_scene_gltf2.html).
+
 ## FAQ
 ### How to resolve conflictions occurred at yarn.lock?
 
diff --git a/gulpfile.ts b/gulpfile.ts
index 85305cd0e44d6c0ef215436c49001a99c5287e89..6ed597731f2db58c45702b86a0cd82d7b232244a 100644
--- a/gulpfile.ts
+++ b/gulpfile.ts
@@ -47,7 +47,11 @@ gulp.task('build:copy:views', () =>
 	gulp.src('./src/server/web/views/**/*').pipe(gulp.dest('./built/server/web/views'))
 );
 
-gulp.task('build:copy', gulp.parallel('build:copy:views', () =>
+gulp.task('build:copy:fonts', () =>
+	gulp.src('./node_modules/three/examples/fonts/**/*').pipe(gulp.dest('./built/client/assets/fonts/'))
+);
+
+gulp.task('build:copy', gulp.parallel('build:copy:views', 'build:copy:fonts', () =>
 	gulp.src([
 		'./src/const.json',
 		'./src/server/web/views/**/*',
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index dc9dd87a7f54c3caf279ac16fdf6d9084af5d566..8a34ea935b10cba3a02c7a9b4de2cf0901fd0697 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -308,6 +308,16 @@ common:
     saved: "保存しました"
     home-profile: "ホームのプロファイル"
     deck-profile: "デッキのプロファイル"
+    room: "ルーム"
+    _room:
+      graphicsQuality: "グラフィックの品質"
+      _graphicsQuality:
+        ultra: "最高"
+        high: "高"
+        medium: "中"
+        low: "低"
+        cheep: "最低"
+      useOrthographicCamera: "平行投影カメラを使用"
 
   search: "検索"
   delete: "削除"
@@ -1250,6 +1260,7 @@ desktop/views/components/ui.header.account.vue:
   groups: "グループ"
   follow-requests: "フォロー申請"
   admin: "管理"
+  room: "ルーム"
 
 desktop/views/components/ui.header.nav.vue:
   game: "ゲーム"
@@ -2281,3 +2292,55 @@ pages:
     enviromentVariables: "環境変数"
     pageVariables: "ページ要素"
     argVariables: "入力スロット"
+
+room:
+  add-furniture: "家具を置く"
+  translate: "移動"
+  rotate: "回転"
+  exit: "戻る"
+  remove: "しまう"
+  save: "保存"
+  chooseImage: "画像を選択"
+  room-type: "部屋のタイプ"
+  carpet-color: "床の色"
+  rooms:
+    default: "デフォルト"
+    washitsu: "和室"
+  furnitures:
+    milk: "牛乳パック"
+    bed: "ベッド"
+    low-table: "ローテーブル"
+    desk: "デスク"
+    chair: "チェア"
+    chair2: "チェア2"
+    fan: "換気扇"
+    pc: "パソコン"
+    plant: "観葉植物"
+    plant2: "観葉植物2"
+    eraser: "消しゴム"
+    pencil: "鉛筆"
+    pudding: "プリン"
+    cardboard-box: "段ボール箱"
+    cardboard-box2: "段ボール箱2"
+    cardboard-box3: "段ボール箱3"
+    book: "本"
+    book2: "本2"
+    piano: "ピアノ"
+    facial-tissue: "ティッシュボックス"
+    server: "サーバー"
+    moon: "月"
+    corkboard: "コルクボード"
+    mousepad: "マウスパッド"
+    monitor: "モニター"
+    keyboard: "キーボード"
+    carpet-stripe: "カーペット(縞)"
+    mat: "マット"
+    color-box: "カラーボックス"
+    wall-clock: "壁掛け時計"
+    photoframe: "額縁"
+    cube: "キューブ"
+    tv: "テレビ"
+    pinguin: "ピンギン"
+    rubik-cube: "ルービックキューブ"
+    poster-h: "ポスター(横長)"
+    poster-v: "ポスター(縦長)"
diff --git a/migration/1565634203341-room.ts b/migration/1565634203341-room.ts
new file mode 100644
index 0000000000000000000000000000000000000000..783b375ff9a93be56dc80be8787d7a876ee09ca1
--- /dev/null
+++ b/migration/1565634203341-room.ts
@@ -0,0 +1,13 @@
+import {MigrationInterface, QueryRunner} from "typeorm";
+
+export class room1565634203341 implements MigrationInterface {
+
+    public async up(queryRunner: QueryRunner): Promise<any> {
+        await queryRunner.query(`ALTER TABLE "user_profile" ADD "room" jsonb NOT NULL DEFAULT '{}'`);
+    }
+
+    public async down(queryRunner: QueryRunner): Promise<any> {
+        await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "room"`);
+    }
+
+}
diff --git a/package.json b/package.json
index 5cd0299c0eb860d7ed19715ee332f7b2eacba7d2..85beac348cb57e2e0648cf7cadeb452fb72bbc63 100644
--- a/package.json
+++ b/package.json
@@ -223,6 +223,7 @@
 		"syuilo-password-strength": "0.0.1",
 		"terser-webpack-plugin": "1.3.0",
 		"textarea-caret": "3.1.0",
+		"three": "0.107.0",
 		"tinycolor2": "1.4.1",
 		"tmp": "0.1.0",
 		"ts-loader": "5.3.3",
diff --git a/src/client/app/common/scripts/room/furniture.ts b/src/client/app/common/scripts/room/furniture.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7734e32668699547a00291fef0d828190f1ed209
--- /dev/null
+++ b/src/client/app/common/scripts/room/furniture.ts
@@ -0,0 +1,21 @@
+export type RoomInfo = {
+	roomType: string;
+	carpetColor: string;
+	furnitures: Furniture[];
+};
+
+export type Furniture = {
+	id: string; // 同じ家具が複数ある場合にそれぞれを識別するためのIDであり、家具IDではない
+	type: string; // こっちが家具ID(chairとか)
+	position: {
+		x: number;
+		y: number;
+		z: number;
+	};
+	rotation: {
+		x: number;
+		y: number;
+		z: number;
+	};
+	props?: Record<string, any>;
+};
diff --git a/src/client/app/common/scripts/room/furnitures.json5 b/src/client/app/common/scripts/room/furnitures.json5
new file mode 100644
index 0000000000000000000000000000000000000000..d88228ca36e190076a0bf596869d34ff94a6d8c7
--- /dev/null
+++ b/src/client/app/common/scripts/room/furnitures.json5
@@ -0,0 +1,324 @@
+// 家具メタデータ
+
+// 家具にはユーザーが設定できるプロパティを設定可能です:
+//
+// props: {
+//   <propname>: <proptype>
+// }
+//
+// proptype一覧:
+// * image ... 画像選択ダイアログを出し、その画像のURLが格納されます
+// * color ... 色選択コントロールを出し、選択された色が格納されます
+
+// 家具にカスタムテクスチャを適用できるようにするには、textureプロパティに以下の追加の情報を含めます:
+// 便宜上そのUVのどの部分にカスタムテクスチャを貼り合わせるかのエリアをテクスチャエリアと呼びます。
+// UVは1024*1024だと仮定します。
+//
+// <key>: {
+//   prop: <プロパティ名>,
+//   uv: {
+//     x: <テクスチャエリアX座標>,
+//     y: <テクスチャエリアY座標>,
+//     width: <テクスチャエリアの幅>,
+//     height: <テクスチャエリアの高さ>,
+//   },
+// }
+//
+// <key>には、カスタムテクスチャを適用したいメッシュ名を指定します
+// <プロパティ名>には、カスタムテクスチャとして使用する画像を格納するプロパティ(前述)名を指定します
+
+// 家具にカスタムカラーを適用できるようにするには、colorプロパティに以下の追加の情報を含めます:
+//
+// <key>: <プロパティ名>
+//
+// <key>には、カスタムカラーを適用したいマテリアル名を指定します
+// <プロパティ名>には、カスタムカラーとして使用する色を格納するプロパティ(前述)名を指定します
+
+[
+	{
+		id: "milk",
+		place: "floor"
+	},
+	{
+		id: "bed",
+		place: "floor"
+	},
+	{
+		id: "low-table",
+		place: "floor",
+		props: {
+			color: 'color'
+		},
+		color: {
+			Table: 'color'
+		}
+	},
+	{
+		id: "desk",
+		place: "floor",
+		props: {
+			color: 'color'
+		},
+		color: {
+			Board: 'color'
+		}
+	},
+	{
+		id: "chair",
+		place: "floor",
+		props: {
+			color: 'color'
+		},
+		color: {
+			Chair: 'color'
+		}
+	},
+	{
+		id: "chair2",
+		place: "floor",
+		props: {
+			color1: 'color',
+			color2: 'color'
+		},
+		color: {
+			Cushion: 'color1',
+			Leg: 'color2'
+		}
+	},
+	{
+		id: "fan",
+		place: "wall"
+	},
+	{
+		id: "pc",
+		place: "floor"
+	},
+	{
+		id: "plant",
+		place: "floor"
+	},
+	{
+		id: "plant2",
+		place: "floor"
+	},
+	{
+		id: "eraser",
+		place: "floor"
+	},
+	{
+		id: "pencil",
+		place: "floor"
+	},
+	{
+		id: "pudding",
+		place: "floor"
+	},
+	{
+		id: "cardboard-box",
+		place: "floor"
+	},
+	{
+		id: "cardboard-box2",
+		place: "floor"
+	},
+	{
+		id: "cardboard-box3",
+		place: "floor"
+	},
+	{
+		id: "book",
+		place: "floor",
+		props: {
+			color: 'color'
+		},
+		color: {
+			Cover: 'color'
+		}
+	},
+	{
+		id: "book2",
+		place: "floor"
+	},
+	{
+		id: "piano",
+		place: "floor"
+	},
+	{
+		id: "facial-tissue",
+		place: "floor"
+	},
+	{
+		id: "server",
+		place: "floor"
+	},
+	{
+		id: "moon",
+		place: "floor"
+	},
+	{
+		id: "corkboard",
+		place: "wall"
+	},
+	{
+		id: "mousepad",
+		place: "floor",
+		props: {
+			color: 'color'
+		},
+		color: {
+			Pad: 'color'
+		}
+	},
+	{
+		id: "monitor",
+		place: "floor",
+		props: {
+			screen: 'image'
+		},
+		texture: {
+			Screen: {
+				prop: 'screen',
+				uv: {
+					x: 0,
+					y: 434,
+					width: 1024,
+					height: 588,
+				},
+			},
+		},
+	},
+	{
+		id: "tv",
+		place: "floor",
+		props: {
+			screen: 'image'
+		},
+		texture: {
+			Screen: {
+				prop: 'screen',
+				uv: {
+					x: 0,
+					y: 434,
+					width: 1024,
+					height: 588,
+				},
+			},
+		},
+	},
+	{
+		id: "keyboard",
+		place: "floor"
+	},
+	{
+		id: "carpet-stripe",
+		place: "floor",
+		props: {
+			color1: 'color',
+			color2: 'color'
+		},
+		color: {
+			CarpetAreaA: 'color1',
+			CarpetAreaB: 'color2'
+		},
+	},
+	{
+		id: "mat",
+		place: "floor",
+		props: {
+			color: 'color'
+		},
+		color: {
+			Mat: 'color'
+		}
+	},
+	{
+		id: "color-box",
+		place: "floor",
+		props: {
+			color: 'color'
+		},
+		color: {
+			main: 'color'
+		}
+	},
+	{
+		id: "wall-clock",
+		place: "wall"
+	},
+	{
+		id: "cube",
+		place: "floor",
+		props: {
+			color: 'color'
+		},
+		color: {
+			Cube: 'color'
+		}
+	},
+	{
+		id: "photoframe",
+		place: "wall",
+		props: {
+			photo: 'image',
+			color: 'color'
+		},
+		texture: {
+			Photo: {
+				prop: 'photo',
+				uv: {
+					x: 0,
+					y: 342,
+					width: 1024,
+					height: 683,
+				},
+			},
+		},
+		color: {
+			Frame: 'color'
+		}
+	},
+	{
+		id: "pinguin",
+		place: "floor"
+	},
+	{
+		id: "rubik-cube",
+		place: "floor",
+	},
+	{
+		id: "poster-h",
+		place: "wall",
+		props: {
+			picture: 'image'
+		},
+		texture: {
+			Poster: {
+				prop: 'picture',
+				uv: {
+					x: 0,
+					y: 277,
+					width: 1024,
+					height: 745,
+				},
+			},
+		},
+	},
+	{
+		id: "poster-v",
+		place: "wall",
+		props: {
+			picture: 'image'
+		},
+		texture: {
+			Poster: {
+				prop: 'picture',
+				uv: {
+					x: 0,
+					y: 0,
+					width: 745,
+					height: 1024,
+				},
+			},
+		},
+	},
+]
diff --git a/src/client/app/common/scripts/room/room.ts b/src/client/app/common/scripts/room/room.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2a06f5bc5b86169569cc314bac07bff5a1bedece
--- /dev/null
+++ b/src/client/app/common/scripts/room/room.ts
@@ -0,0 +1,690 @@
+import autobind from 'autobind-decorator';
+import * as THREE from 'three';
+import { GLTFLoader, GLTF } from 'three/examples/jsm/loaders/GLTFLoader';
+import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
+import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';
+import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js';
+import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js';
+import { BloomPass } from 'three/examples/jsm/postprocessing/BloomPass.js';
+import { FXAAShader } from 'three/examples/jsm/shaders/FXAAShader.js';
+import { TransformControls } from 'three/examples/jsm/controls/TransformControls.js';
+import { Furniture, RoomInfo } from './furniture';
+import { v4 as uuid } from 'uuid';
+const furnitureDefs = require('./furnitures.json5');
+
+THREE.ImageUtils.crossOrigin = '';
+
+type Options = {
+	graphicsQuality: Room['graphicsQuality'];
+	onChangeSelect: Room['onChangeSelect'];
+	useOrthographicCamera: boolean;
+};
+
+export class Room {
+	private clock: THREE.Clock;
+	private scene: THREE.Scene;
+	private renderer: THREE.WebGLRenderer;
+	private camera: THREE.PerspectiveCamera | THREE.OrthographicCamera;
+	private controls: OrbitControls;
+	private composer: EffectComposer;
+	private mixers: THREE.AnimationMixer[] = [];
+	private furnitureControl: TransformControls;
+	private roomInfo: RoomInfo;
+	private graphicsQuality: 'cheep' | 'low' | 'medium' | 'high' | 'ultra';
+	private roomObj: THREE.Object3D;
+	private objects: THREE.Object3D[] = [];
+	private selectedObject: THREE.Object3D = null;
+	private onChangeSelect: Function;
+	private isTransformMode = false;
+	public canvas: HTMLCanvasElement;
+
+	private get furnitures(): Furniture[] {
+		return this.roomInfo.furnitures;
+	}
+
+	private set furnitures(furnitures: Furniture[]) {
+		this.roomInfo.furnitures = furnitures;
+	}
+
+	private get enableShadow() {
+		return this.graphicsQuality != 'cheep';
+	}
+
+	private get usePostFXs() {
+		return this.graphicsQuality !== 'cheep' && this.graphicsQuality !== 'low';
+	}
+
+	private get shadowQuality() {
+		return (
+			this.graphicsQuality === 'ultra' ? 16384 :
+			this.graphicsQuality === 'high' ? 8192 :
+			this.graphicsQuality === 'medium' ? 4096 :
+			this.graphicsQuality === 'low' ? 1024 :
+			0); // cheep
+	}
+
+	constructor(user, isMyRoom, roomInfo: RoomInfo, container, options: Options) {
+		this.roomInfo = roomInfo;
+		this.graphicsQuality = options.graphicsQuality;
+		this.onChangeSelect = options.onChangeSelect;
+
+		this.clock = new THREE.Clock(true);
+
+		//#region Init a scene
+		this.scene = new THREE.Scene();
+
+		const width = window.innerWidth;
+		const height = window.innerHeight;
+
+		//#region Init a renderer
+		this.renderer = new THREE.WebGLRenderer({
+			antialias: false,
+			stencil: false,
+			alpha: false,
+			powerPreference:
+				this.graphicsQuality === 'ultra' ? 'high-performance' :
+				this.graphicsQuality === 'high' ? 'high-performance' :
+				this.graphicsQuality === 'medium' ? 'default' :
+				this.graphicsQuality === 'low' ? 'low-power' :
+				'low-power' // cheep
+		});
+
+		this.renderer.setPixelRatio(window.devicePixelRatio);
+		this.renderer.setSize(width, height);
+		this.renderer.autoClear = false;
+		this.renderer.setClearColor(new THREE.Color(0x051f2d));
+		this.renderer.shadowMap.enabled = this.enableShadow;
+		this.renderer.gammaOutput = true;
+		this.renderer.shadowMap.type =
+			this.graphicsQuality === 'ultra' ? THREE.PCFSoftShadowMap :
+			this.graphicsQuality === 'high' ? THREE.PCFSoftShadowMap :
+			this.graphicsQuality === 'medium' ? THREE.PCFShadowMap :
+			this.graphicsQuality === 'low' ? THREE.BasicShadowMap :
+			THREE.BasicShadowMap; // cheep
+
+		this.canvas = this.renderer.domElement;
+		container.appendChild(this.renderer.domElement);
+		//#endregion
+
+		//#region Init a camera
+		this.camera = options.useOrthographicCamera
+			? new THREE.OrthographicCamera(
+				width / - 2, width / 2, height / 2, height / - 2, -10, 10)
+			: new THREE.PerspectiveCamera(45, width / height);
+
+		if (options.useOrthographicCamera) {
+			this.camera.position.x = 2;
+			this.camera.position.y = 2;
+			this.camera.position.z = 2;
+			this.camera.zoom = 100;
+			this.camera.updateProjectionMatrix();
+		} else {
+			this.camera.position.x = 5;
+			this.camera.position.y = 2;
+			this.camera.position.z = 5;
+		}
+
+		this.scene.add(this.camera);
+		//#endregion
+
+		//#region AmbientLight
+		const ambientLight = new THREE.AmbientLight(0xffffff, 1);
+		this.scene.add(ambientLight);
+		//#endregion
+
+		if (this.graphicsQuality !== 'cheep') {
+			//#region Room light
+			const roomLight = new THREE.SpotLight(0xffffff, 0.1);
+
+			roomLight.position.set(0, 8, 0);
+			roomLight.castShadow = this.enableShadow;
+			roomLight.shadow.bias = -0.0001;
+			roomLight.shadow.mapSize.width = this.shadowQuality;
+			roomLight.shadow.mapSize.height = this.shadowQuality;
+			roomLight.shadow.camera.near = 0.1;
+			roomLight.shadow.camera.far = 9;
+			roomLight.shadow.camera.fov = 45;
+
+			this.scene.add(roomLight);
+			//#endregion
+		}
+
+		//#region Out light
+		const outLight = new THREE.SpotLight(0xffffff, 0.4);
+
+		outLight.position.set(9, 3, -2);
+		outLight.castShadow = this.enableShadow;
+		outLight.shadow.bias = -0.001; // アクネ、アーチファクト対策 その代わりピーターパンが発生する可能性がある
+		outLight.shadow.mapSize.width = this.shadowQuality;
+		outLight.shadow.mapSize.height = this.shadowQuality;
+		outLight.shadow.camera.near = 6;
+		outLight.shadow.camera.far = 15;
+		outLight.shadow.camera.fov = 45;
+
+		this.scene.add(outLight);
+		//#endregion
+
+		//#region Init a controller
+		this.controls = new OrbitControls(this.camera, this.renderer.domElement);
+
+		this.controls.target.set(0, 1, 0);
+		this.controls.enableZoom = true;
+		this.controls.enablePan = isMyRoom;
+		this.controls.minPolarAngle = 0;
+		this.controls.maxPolarAngle = Math.PI / 2;
+		this.controls.minAzimuthAngle = 0;
+		this.controls.maxAzimuthAngle = Math.PI / 2;
+		this.controls.enableDamping = true;
+		this.controls.dampingFactor = 0.2;
+		this.controls.mouseButtons.LEFT = 1;
+		this.controls.mouseButtons.MIDDLE = 2;
+		this.controls.mouseButtons.RIGHT = 0;
+		//#endregion
+
+		//#region POST FXs
+		if (!this.usePostFXs) {
+			this.composer = null;
+		} else {
+			const renderTarget = new THREE.WebGLRenderTarget(width, height, {
+				minFilter: THREE.LinearFilter,
+				magFilter: THREE.LinearFilter,
+				format: THREE.RGBFormat,
+				stencilBuffer: false,
+			});
+
+			const fxaa = new ShaderPass(FXAAShader);
+			fxaa.uniforms['resolution'].value = new THREE.Vector2(1 / width, 1 / height);
+			fxaa.renderToScreen = true;
+
+			this.composer = new EffectComposer(this.renderer, renderTarget);
+			this.composer.addPass(new RenderPass(this.scene, this.camera));
+			if (this.graphicsQuality === 'ultra') {
+				this.composer.addPass(new BloomPass(0.25, 30, 128.0, 512));
+			}
+			this.composer.addPass(fxaa);
+		}
+		//#endregion
+		//#endregion
+
+		//#region Label
+		//#region Avatar
+		const avatarUrl = user.avatarUrl;
+
+		const iconTexture = new THREE.TextureLoader().load(avatarUrl);
+		iconTexture.wrapS = THREE.RepeatWrapping;
+		iconTexture.wrapT = THREE.RepeatWrapping;
+		iconTexture.anisotropy = 16;
+
+		const avatarMaterial = new THREE.MeshLambertMaterial({
+			emissive: 0x111111,
+			map: iconTexture,
+			side: THREE.DoubleSide,
+			alphaTest: 0.5
+		});
+
+		const iconGeometry = new THREE.PlaneGeometry(1, 1);
+
+		const avatarObject = new THREE.Mesh(iconGeometry, avatarMaterial);
+		avatarObject.position.set(-3, 2.5, 2);
+		avatarObject.rotation.y = Math.PI / 2;
+		avatarObject.castShadow = false;
+
+		this.scene.add(avatarObject);
+		//#endregion
+
+		//#region Username
+		const name = user.username;
+
+		new THREE.FontLoader().load('/assets/fonts/helvetiker_regular.typeface.json', font => {
+			const nameGeometry = new THREE.TextGeometry(name, {
+				size: 0.5,
+				height: 0,
+				curveSegments: 8,
+				font: font,
+				bevelThickness: 0,
+				bevelSize: 0,
+				bevelEnabled: false
+			});
+
+			const nameMaterial = new THREE.MeshLambertMaterial({
+				color: 0xffffff
+			});
+
+			const nameObject = new THREE.Mesh(nameGeometry, nameMaterial);
+			nameObject.position.set(-3, 2.25, 1.25);
+			nameObject.rotation.y = Math.PI / 2;
+			nameObject.castShadow = false;
+
+			this.scene.add(nameObject);
+		});
+		//#endregion
+		//#endregion
+
+		//#region Interaction
+		if (isMyRoom) {
+			this.furnitureControl = new TransformControls(this.camera, this.renderer.domElement);
+			this.scene.add(this.furnitureControl);
+
+			// Hover highlight
+			this.renderer.domElement.onmousemove = this.onmousemove;
+
+			// Click
+			this.renderer.domElement.onmousedown = this.onmousedown;
+		}
+		//#endregion
+
+		//#region Init room
+		this.loadRoom();
+		//#endregion
+
+		//#region Load furnitures
+		for (const furniture of this.furnitures) {
+			this.loadFurniture(furniture).then(obj => {
+				this.scene.add(obj.scene);
+				this.objects.push(obj.scene);
+			});
+		}
+		//#endregion
+
+		// Start render
+		if (this.usePostFXs) {
+			this.renderWithPostFXs();
+		} else {
+			this.renderWithoutPostFXs();
+		}
+	}
+
+	@autobind
+	private renderWithoutPostFXs() {
+		requestAnimationFrame(this.renderWithoutPostFXs);
+
+		// Update animations
+		const clock = this.clock.getDelta();
+		for (const mixer of this.mixers) {
+			mixer.update(clock);
+		}
+
+		this.controls.update();
+		this.renderer.render(this.scene, this.camera);
+	}
+
+	@autobind
+	private renderWithPostFXs() {
+		requestAnimationFrame(this.renderWithPostFXs);
+
+		// Update animations
+		const clock = this.clock.getDelta();
+		for (const mixer of this.mixers) {
+			mixer.update(clock);
+		}
+
+		this.controls.update();
+		this.renderer.clear();
+		this.composer.render();
+	}
+
+	@autobind
+	private loadRoom() {
+		new GLTFLoader().load(`/assets/room/rooms/${this.roomInfo.roomType}/${this.roomInfo.roomType}.glb`, gltf => {
+			gltf.scene.traverse(child => {
+				if (!(child instanceof THREE.Mesh)) return;
+
+				child.receiveShadow = this.enableShadow;
+
+				child.material = new THREE.MeshLambertMaterial({
+					color: (child.material as THREE.MeshStandardMaterial).color,
+					map: (child.material as THREE.MeshStandardMaterial).map,
+					name: (child.material as THREE.MeshStandardMaterial).name,
+				});
+
+				// 異方性フィルタリング
+				if ((child.material as THREE.MeshLambertMaterial).map && this.graphicsQuality !== 'cheep') {
+					(child.material as THREE.MeshLambertMaterial).map.minFilter = THREE.LinearMipMapLinearFilter;
+					(child.material as THREE.MeshLambertMaterial).map.magFilter = THREE.LinearMipMapLinearFilter;
+					(child.material as THREE.MeshLambertMaterial).map.anisotropy = 8;
+				}
+			});
+
+			gltf.scene.position.set(0, 0, 0);
+
+			this.scene.add(gltf.scene);
+			this.roomObj = gltf.scene;
+			if (this.roomInfo.roomType === 'default') {
+				this.applyCarpetColor();
+			}
+		});
+	}
+
+	@autobind
+	private loadFurniture(furniture: Furniture) {
+		const def = furnitureDefs.find(d => d.id === furniture.type);
+		return new Promise<GLTF>((res, rej) => {
+			const loader = new GLTFLoader();
+			loader.load(`/assets/room/furnitures/${furniture.type}/${furniture.type}.glb`, gltf => {
+				const model = gltf.scene;
+
+				// Load animation
+				if (gltf.animations.length > 0) {
+					const mixer = new THREE.AnimationMixer(model);
+					this.mixers.push(mixer);
+					for (const clip of gltf.animations) {
+						mixer.clipAction(clip).play();
+					}
+				}
+
+				model.name = furniture.id;
+				model.position.x = furniture.position.x;
+				model.position.y = furniture.position.y;
+				model.position.z = furniture.position.z;
+				model.rotation.x = furniture.rotation.x;
+				model.rotation.y = furniture.rotation.y;
+				model.rotation.z = furniture.rotation.z;
+
+				model.traverse(child => {
+					if (!(child instanceof THREE.Mesh)) return;
+					child.castShadow = this.enableShadow;
+					child.receiveShadow = this.enableShadow;
+					child.material = new THREE.MeshLambertMaterial({
+						color: (child.material as THREE.MeshStandardMaterial).color,
+						map: (child.material as THREE.MeshStandardMaterial).map,
+						name: (child.material as THREE.MeshStandardMaterial).name,
+					});
+
+					// 異方性フィルタリング
+					if ((child.material as THREE.MeshLambertMaterial).map && this.graphicsQuality !== 'cheep') {
+						(child.material as THREE.MeshLambertMaterial).map.minFilter = THREE.LinearMipMapLinearFilter;
+						(child.material as THREE.MeshLambertMaterial).map.magFilter = THREE.LinearMipMapLinearFilter;
+						(child.material as THREE.MeshLambertMaterial).map.anisotropy = 8;
+					}
+				});
+
+				if (def.color) { // カスタムカラー
+					this.applyCustomColor(model);
+				}
+
+				if (def.texture) { // カスタムテクスチャ
+					this.applyCustomTexture(model);
+				}
+
+				res(gltf);
+			}, null, rej);
+		});
+	}
+
+	@autobind
+	private applyCarpetColor() {
+		this.roomObj.traverse(child => {
+			if (!(child instanceof THREE.Mesh)) return;
+			if (child.material && (child.material as THREE.MeshStandardMaterial).name && (child.material as THREE.MeshStandardMaterial).name === 'Carpet') {
+				(child.material as THREE.MeshStandardMaterial).color.setHex(parseInt(this.roomInfo.carpetColor.substr(1), 16));
+			}
+		});
+	}
+
+	@autobind
+	public applyCustomColor(model: THREE.Object3D) {
+		const furniture = this.furnitures.find(furniture => furniture.id === model.name);
+		const def = furnitureDefs.find(d => d.id === furniture.type);
+		if (def.color == null) return;
+		model.traverse(child => {
+			if (!(child instanceof THREE.Mesh)) return;
+			for (const t of Object.keys(def.color)) {
+				if (!child.material || !(child.material as THREE.MeshStandardMaterial).name || (child.material as THREE.MeshStandardMaterial).name !== t) continue;
+
+				const prop = def.color[t];
+				const val = furniture.props ? furniture.props[prop] : undefined;
+
+				if (val == null) continue;
+
+				(child.material as THREE.MeshStandardMaterial).color.setHex(parseInt(val.substr(1), 16));
+			}
+		});
+	}
+
+	@autobind
+	public applyCustomTexture(model: THREE.Object3D) {
+		const furniture = this.furnitures.find(furniture => furniture.id === model.name);
+		const def = furnitureDefs.find(d => d.id === furniture.type);
+		if (def.texture == null) return;
+		model.traverse(child => {
+			if (!(child instanceof THREE.Mesh)) return;
+			for (const t of Object.keys(def.texture)) {
+				if (child.name !== t) continue;
+
+				const prop = def.texture[t].prop;
+				const val = furniture.props ? furniture.props[prop] : undefined;
+
+				if (val == null) continue;
+
+				const canvas = document.createElement('canvas');
+				canvas.height = 1024;
+				canvas.width = 1024;
+
+				child.material = new THREE.MeshLambertMaterial({
+					emissive: 0x111111,
+					side: THREE.DoubleSide,
+					alphaTest: 0.5,
+				});
+
+				const img = new Image();
+				img.onload = () => {
+					const uvInfo = def.texture[t].uv;
+
+					const ctx = canvas.getContext('2d');
+					ctx.drawImage(img, 0, 0, img.width, img.height, uvInfo.x, uvInfo.y, uvInfo.width, uvInfo.height);
+
+					const texture = new THREE.Texture(canvas);
+					texture.wrapS = THREE.RepeatWrapping;
+					texture.wrapT = THREE.RepeatWrapping;
+					texture.anisotropy = 16;
+					texture.flipY = false;
+
+					(child.material as THREE.MeshLambertMaterial).map = texture;
+					(child.material as THREE.MeshLambertMaterial).needsUpdate = true;
+					(child.material as THREE.MeshLambertMaterial).map.needsUpdate = true;
+				};
+				img.src = val;
+			}
+		});
+	}
+
+	@autobind
+	private onmousemove(ev: MouseEvent) {
+		if (this.isTransformMode) return;
+
+		const rect = (ev.target as HTMLElement).getBoundingClientRect();
+		const x = (((ev.clientX * window.devicePixelRatio) - rect.left) / this.renderer.domElement.width) * 2 - 1;
+		const y = -(((ev.clientY * window.devicePixelRatio) - rect.top) / this.renderer.domElement.height) * 2 + 1;
+		const pos = new THREE.Vector2(x, y);
+
+		this.camera.updateMatrixWorld();
+
+		const raycaster = new THREE.Raycaster();
+		raycaster.setFromCamera(pos, this.camera);
+
+		const intersects = raycaster.intersectObjects(this.objects, true);
+
+		for (const object of this.objects) {
+			if (this.isSelectedObject(object)) continue;
+			object.traverse(child => {
+				if (child instanceof THREE.Mesh) {
+					(child.material as THREE.MeshStandardMaterial).emissive.setHex(0x000000);
+				}
+			});
+		}
+
+		if (intersects.length > 0) {
+			const intersected = this.getRoot(intersects[0].object);
+			if (!this.isSelectedObject(intersected)) {
+				intersected.traverse(child => {
+					if (child instanceof THREE.Mesh) {
+						(child.material as THREE.MeshStandardMaterial).emissive.setHex(0x191919);
+					}
+				});
+			}
+		}
+	}
+
+	@autobind
+	private onmousedown(ev: MouseEvent) {
+		if (this.isTransformMode) return;
+		if (ev.target !== this.renderer.domElement || ev.button !== 0) return;
+
+		const rect = (ev.target as HTMLElement).getBoundingClientRect();
+		const x = (((ev.clientX * window.devicePixelRatio) - rect.left) / this.renderer.domElement.width) * 2 - 1;
+		const y = -(((ev.clientY * window.devicePixelRatio) - rect.top) / this.renderer.domElement.height) * 2 + 1;
+		const pos = new THREE.Vector2(x, y);
+
+		this.camera.updateMatrixWorld();
+
+		const raycaster = new THREE.Raycaster();
+		raycaster.setFromCamera(pos, this.camera);
+
+		const intersects = raycaster.intersectObjects(this.objects, true);
+
+		for (const object of this.objects) {
+			object.traverse(child => {
+				if (child instanceof THREE.Mesh) {
+					(child.material as THREE.MeshStandardMaterial).emissive.setHex(0x000000);
+				}
+			});
+		}
+
+		if (intersects.length > 0) {
+			const selectedObj = this.getRoot(intersects[0].object);
+			this.selectFurniture(selectedObj);
+		} else {
+			this.selectedObject = null;
+			this.onChangeSelect(null);
+		}
+	}
+
+	@autobind
+	private getRoot(obj: THREE.Object3D): THREE.Object3D {
+		let found = false;
+		let x = obj.parent;
+		while (!found) {
+			if (x.parent.parent == null) {
+				found = true;
+			} else {
+				x = x.parent;
+			}
+		}
+		return x;
+	}
+
+	@autobind
+	private isSelectedObject(obj: THREE.Object3D): boolean {
+		if (this.selectedObject == null) {
+			return false;
+		} else {
+			return obj.name === this.selectedObject.name;
+		}
+	}
+
+	@autobind
+	private selectFurniture(obj: THREE.Object3D) {
+		this.selectedObject = obj;
+		this.onChangeSelect(obj);
+		obj.traverse(child => {
+			if (child instanceof THREE.Mesh) {
+				(child.material as THREE.MeshStandardMaterial).emissive.setHex(0xff0000);
+			}
+		});
+	}
+
+	@autobind
+	public enterTransformMode(type: 'translate' | 'rotate') {
+		this.isTransformMode = true;
+		this.furnitureControl.setMode(type);
+		this.furnitureControl.attach(this.selectedObject);
+	}
+
+	@autobind
+	public exitTransformMode() {
+		this.isTransformMode = false;
+		this.furnitureControl.detach();
+	}
+
+	@autobind
+	public updateProp(key: string, value: any) {
+		const furniture = this.furnitures.find(furniture => furniture.id === this.selectedObject.name);
+		if (furniture.props == null) furniture.props = {};
+		furniture.props[key] = value;
+		this.applyCustomColor(this.selectedObject);
+		this.applyCustomTexture(this.selectedObject);
+	}
+
+	@autobind
+	public addFurniture(type: string) {
+		const furniture = {
+			id: uuid(),
+			type: type,
+			position: {
+				x: 0,
+				y: 0,
+				z: 0,
+			},
+			rotation: {
+				x: 0,
+				y: 0,
+				z: 0,
+			},
+		};
+
+		this.furnitures.push(furniture);
+
+		this.loadFurniture(furniture).then(obj => {
+			this.scene.add(obj.scene);
+			this.objects.push(obj.scene);
+		});
+	}
+
+	@autobind
+	public removeFurniture() {
+		const obj = this.selectedObject;
+		this.scene.remove(obj);
+		this.objects = this.objects.filter(object => object.name !== obj.name);
+		this.furnitures = this.furnitures.filter(furniture => furniture.id !== obj.name);
+		this.selectedObject = null;
+		this.onChangeSelect(null);
+	}
+
+	@autobind
+	public updateCarpetColor(color: string) {
+		this.roomInfo.carpetColor = color;
+		this.applyCarpetColor();
+	}
+
+	@autobind
+	public changeRoomType(type: string) {
+		this.roomInfo.roomType = type;
+		this.scene.remove(this.roomObj);
+		this.loadRoom();
+	}
+
+	@autobind
+	public getRoomInfo() {
+		for (const obj of this.objects) {
+			const furniture = this.furnitures.find(f => f.id === obj.name);
+			furniture.position.x = obj.position.x;
+			furniture.position.y = obj.position.y;
+			furniture.position.z = obj.position.z;
+			furniture.rotation.x = obj.rotation.x;
+			furniture.rotation.y = obj.rotation.y;
+			furniture.rotation.z = obj.rotation.z;
+		}
+
+		return this.roomInfo;
+	}
+
+	@autobind
+	public getSelectedObject() {
+		return this.selectedObject;
+	}
+
+	@autobind
+	public findFurnitureById(id: string) {
+		return this.furnitures.find(furniture => furniture.id === id);
+	}
+}
diff --git a/src/client/app/common/views/components/settings/settings.vue b/src/client/app/common/views/components/settings/settings.vue
index c609a9672288cf2a397ddd84a2817c42f7fc85eb..914d5bf84f178dda4a2740df48a8dd46a343cc2e 100644
--- a/src/client/app/common/views/components/settings/settings.vue
+++ b/src/client/app/common/views/components/settings/settings.vue
@@ -159,6 +159,19 @@
 					<template #desc>{{ $t('@._settings.paste-dialog-desc') }}</template>
 				</ui-switch>
 			</section>
+
+			<section>
+				<header>{{ $t('@._settings.room') }}</header>
+				<ui-select v-model="roomGraphicsQuality">
+					<template #label>{{ $t('@._settings._room.graphicsQuality') }}</template>
+					<option value="ultra">{{ $t('@._settings._room._graphicsQuality.ultra') }}</option>
+					<option value="high">{{ $t('@._settings._room._graphicsQuality.high') }}</option>
+					<option value="medium">{{ $t('@._settings._room._graphicsQuality.medium') }}</option>
+					<option value="low">{{ $t('@._settings._room._graphicsQuality.low') }}</option>
+					<option value="cheep">{{ $t('@._settings._room._graphicsQuality.cheep') }}</option>
+				</ui-select>
+				<ui-switch v-model="roomUseOrthographicCamera">{{ $t('@._settings._room.useOrthographicCamera') }}</ui-switch>
+			</section>
 		</ui-card>
 
 		<ui-card>
@@ -503,6 +516,16 @@ export default Vue.extend({
 			set(value) { this.$store.dispatch('settings/set', { key: 'iLikeSushi', value }); }
 		},
 
+		roomUseOrthographicCamera: {
+			get() { return this.$store.state.device.roomUseOrthographicCamera; },
+			set(value) { this.$store.commit('device/set', { key: 'roomUseOrthographicCamera', value }); }
+		},
+
+		roomGraphicsQuality: {
+			get() { return this.$store.state.device.roomGraphicsQuality; },
+			set(value) { this.$store.commit('device/set', { key: 'roomGraphicsQuality', value }); }
+		},
+
 		games_reversi_showBoardLabels: {
 			get() { return this.$store.state.settings.gamesReversiShowBoardLabels; },
 			set(value) { this.$store.dispatch('settings/set', { key: 'gamesReversiShowBoardLabels', value }); }
diff --git a/src/client/app/common/views/pages/room/preview.vue b/src/client/app/common/views/pages/room/preview.vue
new file mode 100644
index 0000000000000000000000000000000000000000..3f849d8c0465ba492b0443f60c71c32df08e2e23
--- /dev/null
+++ b/src/client/app/common/views/pages/room/preview.vue
@@ -0,0 +1,98 @@
+<template>
+<canvas width=224 height=128></canvas>
+</template>
+
+<script lang="ts">
+import Vue from 'vue';
+import * as THREE from 'three';
+
+export default Vue.extend({
+	data() {
+		return {
+			selected: null,
+			objectHeight: 0
+		};
+	},
+
+	mounted() {
+		const canvas = this.$el;
+
+		const width = canvas.width;
+		const height = canvas.height;
+
+		const scene = new THREE.Scene();
+
+		const renderer = new THREE.WebGLRenderer({
+			canvas: canvas,
+			antialias: true,
+			alpha: false
+		});
+		renderer.setPixelRatio(window.devicePixelRatio);
+		renderer.setSize(width, height);
+		renderer.setClearColor(0x000000);
+		renderer.autoClear = false;
+		renderer.shadowMap.enabled = true;
+		renderer.shadowMap.cullFace = THREE.CullFaceBack;
+
+		const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 100);
+		camera.zoom = 10;
+		camera.position.x = 0;
+		camera.position.y = 2;
+		camera.position.z = 0;
+		camera.updateProjectionMatrix();
+		scene.add(camera);
+
+		const ambientLight = new THREE.AmbientLight(0xffffff, 1);
+		ambientLight.castShadow = false;
+		scene.add(ambientLight);
+
+		const light = new THREE.PointLight(0xffffff, 1, 100);
+		light.position.set(3, 3, 3);
+		scene.add(light);
+
+		const grid = new THREE.GridHelper(5, 16, 0x444444, 0x222222);
+		scene.add(grid);
+
+		const render = () => {
+			const timer = Date.now() * 0.0004;
+			requestAnimationFrame(render);
+			
+			camera.position.y = 2 + this.objectHeight / 2;
+			camera.position.z = Math.cos(timer) * 10;
+			camera.position.x = Math.sin(timer) * 10;
+			camera.lookAt(new THREE.Vector3(0, this.objectHeight / 2, 0));
+			renderer.render(scene, camera);
+		};
+
+		this.selected = selected => {
+			const obj = selected.clone();
+
+			// Remove current object
+			const current = scene.getObjectByName('obj');
+			if (current != null) {
+				scene.remove(current);
+			}
+
+			// Add new object
+			obj.name = 'obj';
+			obj.position.x = 0;
+			obj.position.y = 0;
+			obj.position.z = 0;
+			obj.rotation.x = 0;
+			obj.rotation.y = 0;
+			obj.rotation.z = 0;
+			obj.traverse(child => {
+				if (child instanceof THREE.Mesh) {
+					child.material = child.material.clone();
+					return child.material.emissive.setHex(0x000000);
+				}
+			});
+			const objectBoundingBox = new THREE.Box3().setFromObject(obj);
+			this.objectHeight = objectBoundingBox.max.y - objectBoundingBox.min.y;
+			scene.add(obj);
+		};
+
+		render();
+	},
+});
+</script>
diff --git a/src/client/app/common/views/pages/room/room.vue b/src/client/app/common/views/pages/room/room.vue
new file mode 100644
index 0000000000000000000000000000000000000000..955e35701e527e031c1d63d648d7762ed6b381ed
--- /dev/null
+++ b/src/client/app/common/views/pages/room/room.vue
@@ -0,0 +1,237 @@
+<template>
+<div class="hveuntkp">
+	<div class="controller" v-if="objectSelected">
+		<section>
+			<p class="name">{{ selectedFurnitureName }}</p>
+			<x-preview ref="preview"/>
+			<template v-if="selectedFurnitureInfo.props">
+				<div v-for="k in Object.keys(selectedFurnitureInfo.props)" :key="k">
+					<p>{{ k }}</p>
+					<template v-if="selectedFurnitureInfo.props[k] === 'image'">
+						<ui-button @click="chooseImage(k)">{{ $t('chooseImage') }}</ui-button>
+					</template>
+					<template v-else-if="selectedFurnitureInfo.props[k] === 'color'">
+						<input type="color" :value="selectedFurnitureProps ? selectedFurnitureProps[k] : null" @change="updateColor(k, $event)"/>
+					</template>
+				</div>
+			</template>
+		</section>
+		<section>
+			<ui-button @click="translate()" :primary="isTranslateMode"><fa :icon="faArrowsAlt"/> {{ $t('translate') }}</ui-button>
+			<ui-button @click="rotate()" :primary="isRotateMode"><fa :icon="faUndo"/> {{ $t('rotate') }}</ui-button>
+			<ui-button v-if="isTranslateMode || isRotateMode" @click="exit()"><fa :icon="faBan"/> {{ $t('exit') }}</ui-button>
+		</section>
+		<section>
+			<ui-button @click="remove()"><fa :icon="faTrashAlt"/> {{ $t('remove') }}</ui-button>
+		</section>
+	</div>
+	<div class="menu">
+		<section>
+			<ui-button @click="add()"><fa :icon="faBoxOpen"/> {{ $t('add-furniture') }}</ui-button>
+		</section>
+		<section>
+			<ui-select :value="roomType" @input="updateRoomType($event)">
+				<template #label>{{ $t('room-type') }}</template>
+				<option value="default">{{ $t('rooms.default') }}</option>
+				<option value="washitsu">{{ $t('rooms.washitsu') }}</option>
+			</ui-select>
+			<label v-if="roomType === 'default'">
+				<span>{{ $t('carpet-color') }}</span>
+				<input type="color" :value="carpetColor" @change="updateCarpetColor($event)"/>
+			</label>
+		</section>
+		<section>
+			<ui-button primary @click="save()"><fa :icon="faSave"/> {{ $t('save') }}</ui-button>
+		</section>
+	</div>
+</div>
+</template>
+
+<script lang="ts">
+import Vue from 'vue';
+import i18n from '../../../../i18n';
+import { Room } from '../../../scripts/room/room';
+import parseAcct from '../../../../../../misc/acct/parse';
+import XPreview from './preview.vue';
+const storeItems = require('../../../scripts/room/furnitures.json5');
+import { faBoxOpen, faUndo, faArrowsAlt, faBan } from '@fortawesome/free-solid-svg-icons';
+import { faSave, faTrashAlt } from '@fortawesome/free-regular-svg-icons';
+
+let room: Room;
+
+export default Vue.extend({
+	i18n: i18n('room'),
+
+	components: {
+		XPreview
+	},
+
+	props: {
+		acct: {
+			type: String,
+			required: true
+		},
+	},
+
+	data() {
+		return {
+			objectSelected: false,
+			selectedFurnitureName: null,
+			selectedFurnitureInfo: null,
+			selectedFurnitureProps: null,
+			roomType: null,
+			carpetColor: null,
+			isTranslateMode: false,
+			isRotateMode: false,
+			faBoxOpen, faSave, faTrashAlt, faUndo, faArrowsAlt, faBan,
+		};
+	},
+
+	async mounted() {
+		const user = await this.$root.api('users/show', {
+			...parseAcct(this.acct)
+		});
+
+		const roomInfo = await this.$root.api('room/show', {
+			userId: user.id
+		});
+
+		this.roomType = roomInfo.roomType;
+		this.carpetColor = roomInfo.carpetColor;
+
+		room = new Room(user, this.$store.getters.isSignedIn && this.$store.state.i.id === user.id, roomInfo, this.$el, {
+			graphicsQuality: this.$store.state.device.roomGraphicsQuality,
+			onChangeSelect: obj => {
+				this.objectSelected = obj != null;
+				if (obj) {
+					const f = room.findFurnitureById(obj.name);
+					this.selectedFurnitureName = this.$t('furnitures.' + f.type);
+					this.selectedFurnitureInfo = storeItems.find(x => x.id === f.type);
+					this.selectedFurnitureProps = f.props
+						? JSON.parse(JSON.stringify(f.props)) // Disable reactivity
+						: null;
+					this.$nextTick(() => {
+						this.$refs.preview.selected(obj);
+					});
+				}
+			},
+			useOrthographicCamera: this.$store.state.device.roomUseOrthographicCamera
+		});
+	},
+
+	methods: {
+		async add() {
+			const { canceled, result: id } = await this.$root.dialog({
+				type: null,
+				title: this.$t('add-furniture'),
+				select: {
+					items: storeItems.map(item => ({
+						value: item.id, text: this.$t('furnitures.' + item.id)
+					}))
+				},
+				showCancelButton: true
+			});
+			if (canceled) return;
+			room.addFurniture(id);
+		},
+
+		remove() {
+			room.removeFurniture();
+		},
+
+		save() {
+			this.$root.api('room/update', {
+				room: room.getRoomInfo()
+			});
+		},
+
+		chooseImage(key) {
+			this.$chooseDriveFile({
+				multiple: false
+			}).then(file => {
+				room.updateProp(key, file.thumbnailUrl);
+				this.$refs.preview.selected(room.getSelectedObject());
+			});
+		},
+
+		updateColor(key, ev) {
+			room.updateProp(key, ev.target.value);
+			this.$refs.preview.selected(room.getSelectedObject());
+		},
+
+		updateCarpetColor(ev) {
+			room.updateCarpetColor(ev.target.value);
+			this.carpetColor = ev.target.value;
+		},
+
+		updateRoomType(type) {
+			room.changeRoomType(type);
+			this.roomType = type;
+		},
+
+		translate() {
+			if (this.isTranslateMode) {
+				this.exit();
+			} else {
+				this.isRotateMode = false;
+				this.isTranslateMode = true;
+				room.enterTransformMode('translate');
+			}
+		},
+
+		rotate() {
+			if (this.isRotateMode) {
+				this.exit();
+			} else {
+				this.isTranslateMode = false;
+				this.isRotateMode = true;
+				room.enterTransformMode('rotate');
+			}
+		},
+
+		exit() {
+			this.isTranslateMode = false;
+			this.isRotateMode = false;
+			room.exitTransformMode();
+		}
+	}
+});
+</script>
+
+<style lang="stylus" scoped>
+.hveuntkp
+	> .controller
+	> .menu
+		position fixed
+		z-index 1
+		padding 16px
+		background var(--face)
+		color var(--text)
+
+		> section
+			padding 16px 0
+
+			&:first-child
+				padding-top 0
+
+			&:last-child
+				padding-bottom 0
+
+			&:not(:last-child)
+				border-bottom solid 1px var(--faceDivider)
+
+	> .controller
+		top 16px
+		left 16px
+		width 256px
+
+		> section
+			> .name
+				margin 0
+
+	> .menu
+		top 16px
+		right 16px
+		width 256px
+	
+</style>
diff --git a/src/client/app/desktop/script.ts b/src/client/app/desktop/script.ts
index 32e4886de313612257fd3cb5e7f5f5e1faad441e..f77a00ae6b00b5ef32d2709c37f1e4cc94129fd4 100644
--- a/src/client/app/desktop/script.ts
+++ b/src/client/app/desktop/script.ts
@@ -187,6 +187,7 @@ init(async (launch, os) => {
 			{ path: '/i/drive/folder/:folder', component: MkDrive },
 			{ path: '/i/settings', component: MkSettings },
 			{ path: '/selectdrive', component: MkSelectDrive },
+			{ path: '/@:acct/room', props: true, component: () => import('../common/views/pages/room/room.vue').then(m => m.default) },
 			{ path: '/share', component: MkShare },
 			{ path: '/games/reversi/:game?', component: MkReversi },
 			{ path: '/authorize-follow', component: MkFollow },
diff --git a/src/client/app/desktop/views/components/ui.header.account.vue b/src/client/app/desktop/views/components/ui.header.account.vue
index 5d7ed8c302ffb4e4a91d48ba514a583773cf7c37..690f3a5587df14f6497adef2da11ee3f3ee9fb5f 100644
--- a/src/client/app/desktop/views/components/ui.header.account.vue
+++ b/src/client/app/desktop/views/components/ui.header.account.vue
@@ -56,6 +56,13 @@
 						<i><fa icon="angle-right"/></i>
 					</router-link>
 				</li>
+				<li>
+					<router-link :to="`/@${ $store.state.i.username }/room`">
+						<i><fa :icon="faDoorOpen" fixed-width/></i>
+						<span>{{ $t('room') }}</span>
+						<i><fa icon="angle-right"/></i>
+					</router-link>
+				</li>
 			</ul>
 			<ul>
 				<li>
@@ -106,7 +113,7 @@ import i18n from '../../../i18n';
 // import MkSettingsWindow from './settings-window.vue';
 import MkDriveWindow from './drive-window.vue';
 import contains from '../../../common/scripts/contains';
-import { faHome, faColumns, faUsers } from '@fortawesome/free-solid-svg-icons';
+import { faHome, faColumns, faUsers, faDoorOpen } from '@fortawesome/free-solid-svg-icons';
 import { faMoon, faSun, faStickyNote } from '@fortawesome/free-regular-svg-icons';
 
 export default Vue.extend({
@@ -114,7 +121,7 @@ export default Vue.extend({
 	data() {
 		return {
 			isOpen: false,
-			faHome, faColumns, faMoon, faSun, faStickyNote, faUsers
+			faHome, faColumns, faMoon, faSun, faStickyNote, faUsers, faDoorOpen
 		};
 	},
 	computed: {
diff --git a/src/client/app/store.ts b/src/client/app/store.ts
index d9f1631a828c1ef483c66ee2410e7096710241a5..92fb1568b035f8b627c7d1994014be02270bb86e 100644
--- a/src/client/app/store.ts
+++ b/src/client/app/store.ts
@@ -76,6 +76,8 @@ const defaultDeviceSettings = {
 	expandUsersPhotos: true,
 	expandUsersActivity: true,
 	enableMobileQuickNotificationView: false,
+	roomGraphicsQuality: 'medium',
+	roomUseOrthographicCamera: true,
 };
 
 export default (os: MiOS) => new Vuex.Store({
diff --git a/src/client/app/tsconfig.json b/src/client/app/tsconfig.json
index 8605da75ab769d2d0de87c5b10488cf5966c7ea4..3ec0271f63af9da39401a1910484dff4bbfeba7e 100644
--- a/src/client/app/tsconfig.json
+++ b/src/client/app/tsconfig.json
@@ -11,11 +11,17 @@
     "sourceMap": false,
     "target": "es2017",
     "module": "esnext",
+    "moduleResolution": "node",
     "removeComments": false,
     "noLib": false,
     "strict": true,
     "strictNullChecks": false,
-    "experimentalDecorators": true
+    "experimentalDecorators": true,
+    "resolveJsonModule": true,
+    "typeRoots": [
+      "node_modules/@types",
+      "src/@types"
+    ]
   },
   "compileOnSave": false,
   "include": [
diff --git a/src/client/assets/room/furnitures/bed/bed.blend b/src/client/assets/room/furnitures/bed/bed.blend
new file mode 100644
index 0000000000000000000000000000000000000000..731df76d0cd735d34f95bfaf05331ca2a6aa6909
Binary files /dev/null and b/src/client/assets/room/furnitures/bed/bed.blend differ
diff --git a/src/client/assets/room/furnitures/bed/bed.glb b/src/client/assets/room/furnitures/bed/bed.glb
new file mode 100644
index 0000000000000000000000000000000000000000..f35ecb9ef4125801c5f2ff036bc7463abfdabe70
Binary files /dev/null and b/src/client/assets/room/furnitures/bed/bed.glb differ
diff --git a/src/client/assets/room/furnitures/book/book.blend b/src/client/assets/room/furnitures/book/book.blend
new file mode 100644
index 0000000000000000000000000000000000000000..0d4899d4aec991e1f53e6f77d8c89d58159bf948
Binary files /dev/null and b/src/client/assets/room/furnitures/book/book.blend differ
diff --git a/src/client/assets/room/furnitures/book/book.glb b/src/client/assets/room/furnitures/book/book.glb
new file mode 100644
index 0000000000000000000000000000000000000000..546893da06b256e8a5e9710525961a01dc9439bb
Binary files /dev/null and b/src/client/assets/room/furnitures/book/book.glb differ
diff --git a/src/client/assets/room/furnitures/book2/barcode.png b/src/client/assets/room/furnitures/book2/barcode.png
new file mode 100644
index 0000000000000000000000000000000000000000..269485422744a5f2e8e248be9fd19a00d31a9fbd
Binary files /dev/null and b/src/client/assets/room/furnitures/book2/barcode.png differ
diff --git a/src/client/assets/room/furnitures/book2/book2.blend b/src/client/assets/room/furnitures/book2/book2.blend
new file mode 100644
index 0000000000000000000000000000000000000000..e0fdb4810197caad280ed2d1e715d9e254827a04
Binary files /dev/null and b/src/client/assets/room/furnitures/book2/book2.blend differ
diff --git a/src/client/assets/room/furnitures/book2/book2.glb b/src/client/assets/room/furnitures/book2/book2.glb
new file mode 100644
index 0000000000000000000000000000000000000000..2b26402f8ca80bf96517241bf1d0ba1c359605e2
Binary files /dev/null and b/src/client/assets/room/furnitures/book2/book2.glb differ
diff --git a/src/client/assets/room/furnitures/book2/texture.afdesign b/src/client/assets/room/furnitures/book2/texture.afdesign
new file mode 100644
index 0000000000000000000000000000000000000000..b63771607a035da290e5e27f7d9b5824857440c2
Binary files /dev/null and b/src/client/assets/room/furnitures/book2/texture.afdesign differ
diff --git a/src/client/assets/room/furnitures/book2/texture.png b/src/client/assets/room/furnitures/book2/texture.png
new file mode 100644
index 0000000000000000000000000000000000000000..f34328cb4a6563fc097b35a0c791814116cc3da6
Binary files /dev/null and b/src/client/assets/room/furnitures/book2/texture.png differ
diff --git a/src/client/assets/room/furnitures/book2/uv.png b/src/client/assets/room/furnitures/book2/uv.png
new file mode 100644
index 0000000000000000000000000000000000000000..9d282d0b6943cb348131ad9cdf031deaae605f78
Binary files /dev/null and b/src/client/assets/room/furnitures/book2/uv.png differ
diff --git a/src/client/assets/room/furnitures/cardboard-box/cardboard-box.blend b/src/client/assets/room/furnitures/cardboard-box/cardboard-box.blend
new file mode 100644
index 0000000000000000000000000000000000000000..3a528de32a376053a00fc5c9564ed9ebca1a011f
Binary files /dev/null and b/src/client/assets/room/furnitures/cardboard-box/cardboard-box.blend differ
diff --git a/src/client/assets/room/furnitures/cardboard-box/cardboard-box.glb b/src/client/assets/room/furnitures/cardboard-box/cardboard-box.glb
new file mode 100644
index 0000000000000000000000000000000000000000..bed372e94f8b7bd18a272e8a38f5835cbc3a20ca
Binary files /dev/null and b/src/client/assets/room/furnitures/cardboard-box/cardboard-box.glb differ
diff --git a/src/client/assets/room/furnitures/cardboard-box2/cardboard-box2.blend b/src/client/assets/room/furnitures/cardboard-box2/cardboard-box2.blend
new file mode 100644
index 0000000000000000000000000000000000000000..5f146267acf22370b4b7a6d60ace5217b3db91f7
Binary files /dev/null and b/src/client/assets/room/furnitures/cardboard-box2/cardboard-box2.blend differ
diff --git a/src/client/assets/room/furnitures/cardboard-box2/cardboard-box2.glb b/src/client/assets/room/furnitures/cardboard-box2/cardboard-box2.glb
new file mode 100644
index 0000000000000000000000000000000000000000..85fcb5c0b60ebf15658dc49b687c7bda51843a5d
Binary files /dev/null and b/src/client/assets/room/furnitures/cardboard-box2/cardboard-box2.glb differ
diff --git a/src/client/assets/room/furnitures/cardboard-box2/texture.png b/src/client/assets/room/furnitures/cardboard-box2/texture.png
new file mode 100644
index 0000000000000000000000000000000000000000..abc1b3de64825ba198993cfa8971e060d035b90c
Binary files /dev/null and b/src/client/assets/room/furnitures/cardboard-box2/texture.png differ
diff --git a/src/client/assets/room/furnitures/cardboard-box2/uv.png b/src/client/assets/room/furnitures/cardboard-box2/uv.png
new file mode 100644
index 0000000000000000000000000000000000000000..104c141b2cd10e9ebb463f71b672306f63d074b4
Binary files /dev/null and b/src/client/assets/room/furnitures/cardboard-box2/uv.png differ
diff --git a/src/client/assets/room/furnitures/cardboard-box3/cardboard-box3.blend b/src/client/assets/room/furnitures/cardboard-box3/cardboard-box3.blend
new file mode 100644
index 0000000000000000000000000000000000000000..00681a3cfd5060e42378fd2e2fe6646074a6f862
Binary files /dev/null and b/src/client/assets/room/furnitures/cardboard-box3/cardboard-box3.blend differ
diff --git a/src/client/assets/room/furnitures/cardboard-box3/cardboard-box3.glb b/src/client/assets/room/furnitures/cardboard-box3/cardboard-box3.glb
new file mode 100644
index 0000000000000000000000000000000000000000..1ef04276894f0c0f7b29533074d5716fb18d66a9
Binary files /dev/null and b/src/client/assets/room/furnitures/cardboard-box3/cardboard-box3.glb differ
diff --git a/src/client/assets/room/furnitures/cardboard-box3/texture.png b/src/client/assets/room/furnitures/cardboard-box3/texture.png
new file mode 100644
index 0000000000000000000000000000000000000000..e68292dce003c6a5e3b98e7fcde9029f07dc58aa
Binary files /dev/null and b/src/client/assets/room/furnitures/cardboard-box3/texture.png differ
diff --git a/src/client/assets/room/furnitures/cardboard-box3/texture.xcf b/src/client/assets/room/furnitures/cardboard-box3/texture.xcf
new file mode 100644
index 0000000000000000000000000000000000000000..7ffb3e3439d30c22841d77510c8d85815ced9475
Binary files /dev/null and b/src/client/assets/room/furnitures/cardboard-box3/texture.xcf differ
diff --git a/src/client/assets/room/furnitures/cardboard-box3/uv.png b/src/client/assets/room/furnitures/cardboard-box3/uv.png
new file mode 100644
index 0000000000000000000000000000000000000000..3ac23ef5844e6d2b324598ea4a309921e995a2b4
Binary files /dev/null and b/src/client/assets/room/furnitures/cardboard-box3/uv.png differ
diff --git a/src/client/assets/room/furnitures/carpet-stripe/carpet-stripe.blend b/src/client/assets/room/furnitures/carpet-stripe/carpet-stripe.blend
new file mode 100644
index 0000000000000000000000000000000000000000..750343d4f00fc9af30c5158cfc0c517d73e2e02a
Binary files /dev/null and b/src/client/assets/room/furnitures/carpet-stripe/carpet-stripe.blend differ
diff --git a/src/client/assets/room/furnitures/carpet-stripe/carpet-stripe.glb b/src/client/assets/room/furnitures/carpet-stripe/carpet-stripe.glb
new file mode 100644
index 0000000000000000000000000000000000000000..3066a69e353c4ab6668f6c010a804ac258e6f26a
Binary files /dev/null and b/src/client/assets/room/furnitures/carpet-stripe/carpet-stripe.glb differ
diff --git a/src/client/assets/room/furnitures/chair/chair.blend b/src/client/assets/room/furnitures/chair/chair.blend
new file mode 100644
index 0000000000000000000000000000000000000000..79c29a840145a21373a2bb7a635b57745a57007f
Binary files /dev/null and b/src/client/assets/room/furnitures/chair/chair.blend differ
diff --git a/src/client/assets/room/furnitures/chair/chair.glb b/src/client/assets/room/furnitures/chair/chair.glb
new file mode 100644
index 0000000000000000000000000000000000000000..08ee1a0bb0fc569dad8b900fb8c0d5ab2cef4716
Binary files /dev/null and b/src/client/assets/room/furnitures/chair/chair.glb differ
diff --git a/src/client/assets/room/furnitures/chair2/chair2.blend b/src/client/assets/room/furnitures/chair2/chair2.blend
new file mode 100644
index 0000000000000000000000000000000000000000..c6a1acd96f5f5347536e31d99ee36d25e8e775d6
Binary files /dev/null and b/src/client/assets/room/furnitures/chair2/chair2.blend differ
diff --git a/src/client/assets/room/furnitures/chair2/chair2.glb b/src/client/assets/room/furnitures/chair2/chair2.glb
new file mode 100644
index 0000000000000000000000000000000000000000..5ea2f3518b692efe6e5237aae464ba594c3f755c
Binary files /dev/null and b/src/client/assets/room/furnitures/chair2/chair2.glb differ
diff --git a/src/client/assets/room/furnitures/color-box/color-box.blend b/src/client/assets/room/furnitures/color-box/color-box.blend
new file mode 100644
index 0000000000000000000000000000000000000000..f96a4ff76633b20fb292504702c0816e06859ce7
Binary files /dev/null and b/src/client/assets/room/furnitures/color-box/color-box.blend differ
diff --git a/src/client/assets/room/furnitures/color-box/color-box.glb b/src/client/assets/room/furnitures/color-box/color-box.glb
new file mode 100644
index 0000000000000000000000000000000000000000..43f2abcae8ff2471236a5936844179edb4f154ec
Binary files /dev/null and b/src/client/assets/room/furnitures/color-box/color-box.glb differ
diff --git a/src/client/assets/room/furnitures/corkboard/corkboard.blend b/src/client/assets/room/furnitures/corkboard/corkboard.blend
new file mode 100644
index 0000000000000000000000000000000000000000..9a7e1878cda339fc9f22151d9904bdddde7f892f
Binary files /dev/null and b/src/client/assets/room/furnitures/corkboard/corkboard.blend differ
diff --git a/src/client/assets/room/furnitures/corkboard/corkboard.glb b/src/client/assets/room/furnitures/corkboard/corkboard.glb
new file mode 100644
index 0000000000000000000000000000000000000000..fee108fb91519ca8ec3f7dc9637db5dde02c6a6d
Binary files /dev/null and b/src/client/assets/room/furnitures/corkboard/corkboard.glb differ
diff --git a/src/client/assets/room/furnitures/cube/cube.blend b/src/client/assets/room/furnitures/cube/cube.blend
new file mode 100644
index 0000000000000000000000000000000000000000..1af5bf40a9d3c63d4a8345bc34b36f4e36f8fdfe
Binary files /dev/null and b/src/client/assets/room/furnitures/cube/cube.blend differ
diff --git a/src/client/assets/room/furnitures/cube/cube.glb b/src/client/assets/room/furnitures/cube/cube.glb
new file mode 100644
index 0000000000000000000000000000000000000000..4ac8b6036d22ea22981582ee87e529277acab4c5
Binary files /dev/null and b/src/client/assets/room/furnitures/cube/cube.glb differ
diff --git a/src/client/assets/room/furnitures/desk/desk.blend b/src/client/assets/room/furnitures/desk/desk.blend
new file mode 100644
index 0000000000000000000000000000000000000000..c88d01f0b233ec40c4475b2555d21386d9037691
Binary files /dev/null and b/src/client/assets/room/furnitures/desk/desk.blend differ
diff --git a/src/client/assets/room/furnitures/desk/desk.glb b/src/client/assets/room/furnitures/desk/desk.glb
new file mode 100644
index 0000000000000000000000000000000000000000..4a58513095884e46661b178bc50b41140eeb3a47
Binary files /dev/null and b/src/client/assets/room/furnitures/desk/desk.glb differ
diff --git a/src/client/assets/room/furnitures/eraser/cover.png b/src/client/assets/room/furnitures/eraser/cover.png
new file mode 100644
index 0000000000000000000000000000000000000000..e64bfde11a56ef8d59c4649e9c9296eefc39cbf3
Binary files /dev/null and b/src/client/assets/room/furnitures/eraser/cover.png differ
diff --git a/src/client/assets/room/furnitures/eraser/cover.psd b/src/client/assets/room/furnitures/eraser/cover.psd
new file mode 100644
index 0000000000000000000000000000000000000000..c393337833d4d76f28a4f2de33ace2ce863f3b1c
Binary files /dev/null and b/src/client/assets/room/furnitures/eraser/cover.psd differ
diff --git a/src/client/assets/room/furnitures/eraser/eraser-uv.png b/src/client/assets/room/furnitures/eraser/eraser-uv.png
new file mode 100644
index 0000000000000000000000000000000000000000..2124114e70b890fb13e78afb1cc25c54df960b06
Binary files /dev/null and b/src/client/assets/room/furnitures/eraser/eraser-uv.png differ
diff --git a/src/client/assets/room/furnitures/eraser/eraser.blend b/src/client/assets/room/furnitures/eraser/eraser.blend
new file mode 100644
index 0000000000000000000000000000000000000000..103c54fbae3f04db34ad3ad7cd36102890a393fb
Binary files /dev/null and b/src/client/assets/room/furnitures/eraser/eraser.blend differ
diff --git a/src/client/assets/room/furnitures/eraser/eraser.glb b/src/client/assets/room/furnitures/eraser/eraser.glb
new file mode 100644
index 0000000000000000000000000000000000000000..016b60df202459182cb66c6f12b13222f8a88ae8
Binary files /dev/null and b/src/client/assets/room/furnitures/eraser/eraser.glb differ
diff --git a/src/client/assets/room/furnitures/facial-tissue/facial-tissue-uv.png b/src/client/assets/room/furnitures/facial-tissue/facial-tissue-uv.png
new file mode 100644
index 0000000000000000000000000000000000000000..b6dfa8f30851575cc183890251a076e9d761f3b0
Binary files /dev/null and b/src/client/assets/room/furnitures/facial-tissue/facial-tissue-uv.png differ
diff --git a/src/client/assets/room/furnitures/facial-tissue/facial-tissue.blend b/src/client/assets/room/furnitures/facial-tissue/facial-tissue.blend
new file mode 100644
index 0000000000000000000000000000000000000000..d59f87c1ee5482a6f375585fba575a60a4d454a8
Binary files /dev/null and b/src/client/assets/room/furnitures/facial-tissue/facial-tissue.blend differ
diff --git a/src/client/assets/room/furnitures/facial-tissue/facial-tissue.glb b/src/client/assets/room/furnitures/facial-tissue/facial-tissue.glb
new file mode 100644
index 0000000000000000000000000000000000000000..48b36ef34729727ff186714aaf6f08f473115eaa
Binary files /dev/null and b/src/client/assets/room/furnitures/facial-tissue/facial-tissue.glb differ
diff --git a/src/client/assets/room/furnitures/facial-tissue/facial-tissue.png b/src/client/assets/room/furnitures/facial-tissue/facial-tissue.png
new file mode 100644
index 0000000000000000000000000000000000000000..f25c882027e0f9b11647291df5cca9b8459a20d1
Binary files /dev/null and b/src/client/assets/room/furnitures/facial-tissue/facial-tissue.png differ
diff --git a/src/client/assets/room/furnitures/facial-tissue/facial-tissue.psd b/src/client/assets/room/furnitures/facial-tissue/facial-tissue.psd
new file mode 100644
index 0000000000000000000000000000000000000000..cd59fc007bb0d4c2472a99ec2326b8f8284cae87
Binary files /dev/null and b/src/client/assets/room/furnitures/facial-tissue/facial-tissue.psd differ
diff --git a/src/client/assets/room/furnitures/fan/fan.blend b/src/client/assets/room/furnitures/fan/fan.blend
new file mode 100644
index 0000000000000000000000000000000000000000..8c8106e5fe61f92fce0e3e3f359bba90474a5576
Binary files /dev/null and b/src/client/assets/room/furnitures/fan/fan.blend differ
diff --git a/src/client/assets/room/furnitures/fan/fan.glb b/src/client/assets/room/furnitures/fan/fan.glb
new file mode 100644
index 0000000000000000000000000000000000000000..d9367f353407b2a9f66841ef2ac373e322ca64b5
Binary files /dev/null and b/src/client/assets/room/furnitures/fan/fan.glb differ
diff --git a/src/client/assets/room/furnitures/keyboard/keyboard.blend b/src/client/assets/room/furnitures/keyboard/keyboard.blend
new file mode 100644
index 0000000000000000000000000000000000000000..ab33d134b3d99491c34b8557628c944948d01ce9
Binary files /dev/null and b/src/client/assets/room/furnitures/keyboard/keyboard.blend differ
diff --git a/src/client/assets/room/furnitures/keyboard/keyboard.glb b/src/client/assets/room/furnitures/keyboard/keyboard.glb
new file mode 100644
index 0000000000000000000000000000000000000000..15dc69f47a4d5390dd2f5e83045cf1577ae98a75
Binary files /dev/null and b/src/client/assets/room/furnitures/keyboard/keyboard.glb differ
diff --git a/src/client/assets/room/furnitures/low-table/low-table.blend b/src/client/assets/room/furnitures/low-table/low-table.blend
new file mode 100644
index 0000000000000000000000000000000000000000..e1592174d9963b9996cdf1969ecc96885c31d2a5
Binary files /dev/null and b/src/client/assets/room/furnitures/low-table/low-table.blend differ
diff --git a/src/client/assets/room/furnitures/low-table/low-table.glb b/src/client/assets/room/furnitures/low-table/low-table.glb
new file mode 100644
index 0000000000000000000000000000000000000000..c69bf35d7b52eda2434c4a3db91a3c5f695e4adf
Binary files /dev/null and b/src/client/assets/room/furnitures/low-table/low-table.glb differ
diff --git a/src/client/assets/room/furnitures/mat/mat.blend b/src/client/assets/room/furnitures/mat/mat.blend
new file mode 100644
index 0000000000000000000000000000000000000000..a1e1a68c554e93b9b63f54801310d6f34a83d161
Binary files /dev/null and b/src/client/assets/room/furnitures/mat/mat.blend differ
diff --git a/src/client/assets/room/furnitures/mat/mat.glb b/src/client/assets/room/furnitures/mat/mat.glb
new file mode 100644
index 0000000000000000000000000000000000000000..87ccd44e1a2fcb07c7ea55c7561bff356c5f6d70
Binary files /dev/null and b/src/client/assets/room/furnitures/mat/mat.glb differ
diff --git a/src/client/assets/room/furnitures/milk/milk-uv.png b/src/client/assets/room/furnitures/milk/milk-uv.png
new file mode 100644
index 0000000000000000000000000000000000000000..70060c91909bab23f3cc677d4878e487170cd023
Binary files /dev/null and b/src/client/assets/room/furnitures/milk/milk-uv.png differ
diff --git a/src/client/assets/room/furnitures/milk/milk.blend b/src/client/assets/room/furnitures/milk/milk.blend
new file mode 100644
index 0000000000000000000000000000000000000000..2df508d5b905dbc1ee0df0f5b755c5949dc66681
Binary files /dev/null and b/src/client/assets/room/furnitures/milk/milk.blend differ
diff --git a/src/client/assets/room/furnitures/milk/milk.glb b/src/client/assets/room/furnitures/milk/milk.glb
new file mode 100644
index 0000000000000000000000000000000000000000..b335fe3d021b227bcec7f82d13133442f1fe84cf
Binary files /dev/null and b/src/client/assets/room/furnitures/milk/milk.glb differ
diff --git a/src/client/assets/room/furnitures/milk/milk.png b/src/client/assets/room/furnitures/milk/milk.png
new file mode 100644
index 0000000000000000000000000000000000000000..2ce010ce785e3029283135be67aa96a45403d967
Binary files /dev/null and b/src/client/assets/room/furnitures/milk/milk.png differ
diff --git a/src/client/assets/room/furnitures/milk/milk.psd b/src/client/assets/room/furnitures/milk/milk.psd
new file mode 100644
index 0000000000000000000000000000000000000000..f31e439277821d8ad26e4a6f68c00d80cac2f8ba
Binary files /dev/null and b/src/client/assets/room/furnitures/milk/milk.psd differ
diff --git a/src/client/assets/room/furnitures/monitor/monitor.blend b/src/client/assets/room/furnitures/monitor/monitor.blend
new file mode 100644
index 0000000000000000000000000000000000000000..bf37cd5da2cc1623d313ef07ffd0025802dadd51
Binary files /dev/null and b/src/client/assets/room/furnitures/monitor/monitor.blend differ
diff --git a/src/client/assets/room/furnitures/monitor/monitor.glb b/src/client/assets/room/furnitures/monitor/monitor.glb
new file mode 100644
index 0000000000000000000000000000000000000000..efe42bd71ceb6fbfa3dff0970c73093e88ca75ee
Binary files /dev/null and b/src/client/assets/room/furnitures/monitor/monitor.glb differ
diff --git a/src/client/assets/room/furnitures/monitor/monitor.psd b/src/client/assets/room/furnitures/monitor/monitor.psd
new file mode 100644
index 0000000000000000000000000000000000000000..57afff9cd9ef9717b57fee8df33bc5bd4814da1e
Binary files /dev/null and b/src/client/assets/room/furnitures/monitor/monitor.psd differ
diff --git a/src/client/assets/room/furnitures/monitor/screen-uv.png b/src/client/assets/room/furnitures/monitor/screen-uv.png
new file mode 100644
index 0000000000000000000000000000000000000000..55990873643265fc53b45e432a485af145f5c0e6
Binary files /dev/null and b/src/client/assets/room/furnitures/monitor/screen-uv.png differ
diff --git a/src/client/assets/room/furnitures/monitor/screen.jpg b/src/client/assets/room/furnitures/monitor/screen.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..6f03a9ee90c5b94108703c5017ce0399472717fe
Binary files /dev/null and b/src/client/assets/room/furnitures/monitor/screen.jpg differ
diff --git a/src/client/assets/room/furnitures/moon/moon.blend b/src/client/assets/room/furnitures/moon/moon.blend
new file mode 100644
index 0000000000000000000000000000000000000000..4ff3deab8ec8c6cdc7ade407b72a8fe5ba28c782
Binary files /dev/null and b/src/client/assets/room/furnitures/moon/moon.blend differ
diff --git a/src/client/assets/room/furnitures/moon/moon.glb b/src/client/assets/room/furnitures/moon/moon.glb
new file mode 100644
index 0000000000000000000000000000000000000000..07fa7e4c02a9b1902ca137b08e10266c040bfa48
Binary files /dev/null and b/src/client/assets/room/furnitures/moon/moon.glb differ
diff --git a/src/client/assets/room/furnitures/moon/moon.jpg b/src/client/assets/room/furnitures/moon/moon.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..1b0798755ce745cd6888215ac59d65b5b6e19190
Binary files /dev/null and b/src/client/assets/room/furnitures/moon/moon.jpg differ
diff --git a/src/client/assets/room/furnitures/mousepad/mousepad.blend b/src/client/assets/room/furnitures/mousepad/mousepad.blend
new file mode 100644
index 0000000000000000000000000000000000000000..14bd139c94a03e0b26685de5431e6fe9893d87ed
Binary files /dev/null and b/src/client/assets/room/furnitures/mousepad/mousepad.blend differ
diff --git a/src/client/assets/room/furnitures/mousepad/mousepad.glb b/src/client/assets/room/furnitures/mousepad/mousepad.glb
new file mode 100644
index 0000000000000000000000000000000000000000..681ada49cd93e18d9b01f111c1de8a0fd6e7b460
Binary files /dev/null and b/src/client/assets/room/furnitures/mousepad/mousepad.glb differ
diff --git a/src/client/assets/room/furnitures/pc/motherboard-uv.png b/src/client/assets/room/furnitures/pc/motherboard-uv.png
new file mode 100644
index 0000000000000000000000000000000000000000..490ae868106d28e00d9af6c84bd7e8f026602be8
Binary files /dev/null and b/src/client/assets/room/furnitures/pc/motherboard-uv.png differ
diff --git a/src/client/assets/room/furnitures/pc/motherboard-uv.psd b/src/client/assets/room/furnitures/pc/motherboard-uv.psd
new file mode 100644
index 0000000000000000000000000000000000000000..971f33f79eadfdda3e206d09a23aa17d04ac16fa
Binary files /dev/null and b/src/client/assets/room/furnitures/pc/motherboard-uv.psd differ
diff --git a/src/client/assets/room/furnitures/pc/motherboard.jpg b/src/client/assets/room/furnitures/pc/motherboard.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..415f8d0b1d1ac3edd7c6ffeb7ab1ae0256a3ec13
Binary files /dev/null and b/src/client/assets/room/furnitures/pc/motherboard.jpg differ
diff --git a/src/client/assets/room/furnitures/pc/pc.blend b/src/client/assets/room/furnitures/pc/pc.blend
new file mode 100644
index 0000000000000000000000000000000000000000..13dfec6ccc27bdd13bfdcb5d9eec42fcaf75b7b0
Binary files /dev/null and b/src/client/assets/room/furnitures/pc/pc.blend differ
diff --git a/src/client/assets/room/furnitures/pc/pc.glb b/src/client/assets/room/furnitures/pc/pc.glb
new file mode 100644
index 0000000000000000000000000000000000000000..44a48b18ae6031bf312552636e50c9014391e667
Binary files /dev/null and b/src/client/assets/room/furnitures/pc/pc.glb differ
diff --git a/src/client/assets/room/furnitures/pencil/pencil.blend b/src/client/assets/room/furnitures/pencil/pencil.blend
new file mode 100644
index 0000000000000000000000000000000000000000..0fc6bdd77699df7874b90b5694ff6e17c0d71a3c
Binary files /dev/null and b/src/client/assets/room/furnitures/pencil/pencil.blend differ
diff --git a/src/client/assets/room/furnitures/pencil/pencil.glb b/src/client/assets/room/furnitures/pencil/pencil.glb
new file mode 100644
index 0000000000000000000000000000000000000000..a938b5cdcc759f46990adfa9e2656587e7070400
Binary files /dev/null and b/src/client/assets/room/furnitures/pencil/pencil.glb differ
diff --git a/src/client/assets/room/furnitures/photoframe/photo-uv.png b/src/client/assets/room/furnitures/photoframe/photo-uv.png
new file mode 100644
index 0000000000000000000000000000000000000000..befa32c3678847f76f2f8ad83afbec879b819c71
Binary files /dev/null and b/src/client/assets/room/furnitures/photoframe/photo-uv.png differ
diff --git a/src/client/assets/room/furnitures/photoframe/photo.jpg b/src/client/assets/room/furnitures/photoframe/photo.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..9597571fef05f4f16330458bc0c936e8750415f3
Binary files /dev/null and b/src/client/assets/room/furnitures/photoframe/photo.jpg differ
diff --git a/src/client/assets/room/furnitures/photoframe/photoframe.blend b/src/client/assets/room/furnitures/photoframe/photoframe.blend
new file mode 100644
index 0000000000000000000000000000000000000000..4224cde45b42b17acd08e3a98eb4d990d543e27f
Binary files /dev/null and b/src/client/assets/room/furnitures/photoframe/photoframe.blend differ
diff --git a/src/client/assets/room/furnitures/photoframe/photoframe.glb b/src/client/assets/room/furnitures/photoframe/photoframe.glb
new file mode 100644
index 0000000000000000000000000000000000000000..4255a77de6ccaacec292f79c0dec650bb22137c7
Binary files /dev/null and b/src/client/assets/room/furnitures/photoframe/photoframe.glb differ
diff --git a/src/client/assets/room/furnitures/piano/piano.blend b/src/client/assets/room/furnitures/piano/piano.blend
new file mode 100644
index 0000000000000000000000000000000000000000..7653cdf672f6599d66e43aa71a1cf657c54e7f6f
Binary files /dev/null and b/src/client/assets/room/furnitures/piano/piano.blend differ
diff --git a/src/client/assets/room/furnitures/piano/piano.glb b/src/client/assets/room/furnitures/piano/piano.glb
new file mode 100644
index 0000000000000000000000000000000000000000..7242e78ceb4a382243d37cc5c326a26684033461
Binary files /dev/null and b/src/client/assets/room/furnitures/piano/piano.glb differ
diff --git a/src/client/assets/room/furnitures/pinguin/pinguin.glb b/src/client/assets/room/furnitures/pinguin/pinguin.glb
new file mode 100644
index 0000000000000000000000000000000000000000..0ddf611ad94ba4530a01301ab467556ad36fc725
Binary files /dev/null and b/src/client/assets/room/furnitures/pinguin/pinguin.glb differ
diff --git a/src/client/assets/room/furnitures/plant/plant-soil-uv.png b/src/client/assets/room/furnitures/plant/plant-soil-uv.png
new file mode 100644
index 0000000000000000000000000000000000000000..ca690fdeb8354088ef0086308df6947133158720
Binary files /dev/null and b/src/client/assets/room/furnitures/plant/plant-soil-uv.png differ
diff --git a/src/client/assets/room/furnitures/plant/plant-soil.png b/src/client/assets/room/furnitures/plant/plant-soil.png
new file mode 100644
index 0000000000000000000000000000000000000000..dc26ba062e4bb923c484a3641bb5e0333a4586a4
Binary files /dev/null and b/src/client/assets/room/furnitures/plant/plant-soil.png differ
diff --git a/src/client/assets/room/furnitures/plant/plant-soil.psd b/src/client/assets/room/furnitures/plant/plant-soil.psd
new file mode 100644
index 0000000000000000000000000000000000000000..1457b7ea5b3086877beb83ab3bb822648c8a8d4f
Binary files /dev/null and b/src/client/assets/room/furnitures/plant/plant-soil.psd differ
diff --git a/src/client/assets/room/furnitures/plant/plant.blend b/src/client/assets/room/furnitures/plant/plant.blend
new file mode 100644
index 0000000000000000000000000000000000000000..aa38c7b54e4e6656a713b97964a1de0b8ea36b48
Binary files /dev/null and b/src/client/assets/room/furnitures/plant/plant.blend differ
diff --git a/src/client/assets/room/furnitures/plant/plant.glb b/src/client/assets/room/furnitures/plant/plant.glb
new file mode 100644
index 0000000000000000000000000000000000000000..38422b4a9b2940de45816684ae45c77599382995
Binary files /dev/null and b/src/client/assets/room/furnitures/plant/plant.glb differ
diff --git a/src/client/assets/room/furnitures/plant2/plant2.blend b/src/client/assets/room/furnitures/plant2/plant2.blend
new file mode 100644
index 0000000000000000000000000000000000000000..6592c5d98d91007550d41cdfd27e3da08f17bb3e
Binary files /dev/null and b/src/client/assets/room/furnitures/plant2/plant2.blend differ
diff --git a/src/client/assets/room/furnitures/plant2/plant2.glb b/src/client/assets/room/furnitures/plant2/plant2.glb
new file mode 100644
index 0000000000000000000000000000000000000000..223e6f58340f9a9ba76c8aac4777ca3ccfb51a0c
Binary files /dev/null and b/src/client/assets/room/furnitures/plant2/plant2.glb differ
diff --git a/src/client/assets/room/furnitures/plant2/soil.png b/src/client/assets/room/furnitures/plant2/soil.png
new file mode 100644
index 0000000000000000000000000000000000000000..dc26ba062e4bb923c484a3641bb5e0333a4586a4
Binary files /dev/null and b/src/client/assets/room/furnitures/plant2/soil.png differ
diff --git a/src/client/assets/room/furnitures/poster-h/poster-h.blend b/src/client/assets/room/furnitures/poster-h/poster-h.blend
new file mode 100644
index 0000000000000000000000000000000000000000..40f944f3c115f9d3aaf966f6b7f06e436c85d9e4
Binary files /dev/null and b/src/client/assets/room/furnitures/poster-h/poster-h.blend differ
diff --git a/src/client/assets/room/furnitures/poster-h/poster-h.glb b/src/client/assets/room/furnitures/poster-h/poster-h.glb
new file mode 100644
index 0000000000000000000000000000000000000000..c6032c100979c713e6d6a4a15f30f7dcec160e6d
Binary files /dev/null and b/src/client/assets/room/furnitures/poster-h/poster-h.glb differ
diff --git a/src/client/assets/room/furnitures/poster-h/uv.png b/src/client/assets/room/furnitures/poster-h/uv.png
new file mode 100644
index 0000000000000000000000000000000000000000..700e6b1cf0b39885d6525705a76822f7da814653
Binary files /dev/null and b/src/client/assets/room/furnitures/poster-h/uv.png differ
diff --git a/src/client/assets/room/furnitures/poster-v/poster-v.blend b/src/client/assets/room/furnitures/poster-v/poster-v.blend
new file mode 100644
index 0000000000000000000000000000000000000000..07fe971634f2f9df6ab8802f670ebd09c41adf65
Binary files /dev/null and b/src/client/assets/room/furnitures/poster-v/poster-v.blend differ
diff --git a/src/client/assets/room/furnitures/poster-v/poster-v.glb b/src/client/assets/room/furnitures/poster-v/poster-v.glb
new file mode 100644
index 0000000000000000000000000000000000000000..6e3782f19364233453bfee6d4af8451614af6de5
Binary files /dev/null and b/src/client/assets/room/furnitures/poster-v/poster-v.glb differ
diff --git a/src/client/assets/room/furnitures/poster-v/uv.png b/src/client/assets/room/furnitures/poster-v/uv.png
new file mode 100644
index 0000000000000000000000000000000000000000..225b0061a2c91eccb48e44abe3f09b3875c26442
Binary files /dev/null and b/src/client/assets/room/furnitures/poster-v/uv.png differ
diff --git a/src/client/assets/room/furnitures/pudding/pudding.blend b/src/client/assets/room/furnitures/pudding/pudding.blend
new file mode 100644
index 0000000000000000000000000000000000000000..bba40ce161d37b08e974cbc875ee5193ffbfe862
Binary files /dev/null and b/src/client/assets/room/furnitures/pudding/pudding.blend differ
diff --git a/src/client/assets/room/furnitures/pudding/pudding.glb b/src/client/assets/room/furnitures/pudding/pudding.glb
new file mode 100644
index 0000000000000000000000000000000000000000..06c9ed80cc1507fc8562d12b7b3cf849897580fa
Binary files /dev/null and b/src/client/assets/room/furnitures/pudding/pudding.glb differ
diff --git a/src/client/assets/room/furnitures/rubik-cube/rubik-cube.blend b/src/client/assets/room/furnitures/rubik-cube/rubik-cube.blend
new file mode 100644
index 0000000000000000000000000000000000000000..6c09067e78bfce812853e715ed6b467fd87ab2d4
Binary files /dev/null and b/src/client/assets/room/furnitures/rubik-cube/rubik-cube.blend differ
diff --git a/src/client/assets/room/furnitures/rubik-cube/rubik-cube.glb b/src/client/assets/room/furnitures/rubik-cube/rubik-cube.glb
new file mode 100644
index 0000000000000000000000000000000000000000..d640df9b06e50757d021866fb6eeda54ad272296
Binary files /dev/null and b/src/client/assets/room/furnitures/rubik-cube/rubik-cube.glb differ
diff --git a/src/client/assets/room/furnitures/server/rack-uv.png b/src/client/assets/room/furnitures/server/rack-uv.png
new file mode 100644
index 0000000000000000000000000000000000000000..c317bd0b1a08a3b7d5470b0fd52e1345d977365c
Binary files /dev/null and b/src/client/assets/room/furnitures/server/rack-uv.png differ
diff --git a/src/client/assets/room/furnitures/server/rack.png b/src/client/assets/room/furnitures/server/rack.png
new file mode 100644
index 0000000000000000000000000000000000000000..2c8231f878990916fbba92549d4ab730c48e62d7
Binary files /dev/null and b/src/client/assets/room/furnitures/server/rack.png differ
diff --git a/src/client/assets/room/furnitures/server/server.blend b/src/client/assets/room/furnitures/server/server.blend
new file mode 100644
index 0000000000000000000000000000000000000000..6675dfbdc2f020c98cb755bd1989de2a753b107a
Binary files /dev/null and b/src/client/assets/room/furnitures/server/server.blend differ
diff --git a/src/client/assets/room/furnitures/server/server.glb b/src/client/assets/room/furnitures/server/server.glb
new file mode 100644
index 0000000000000000000000000000000000000000..a8b530a2d22ee5cce3e76ac644c55f64e7e2f041
Binary files /dev/null and b/src/client/assets/room/furnitures/server/server.glb differ
diff --git a/src/client/assets/room/furnitures/server/server.png b/src/client/assets/room/furnitures/server/server.png
new file mode 100644
index 0000000000000000000000000000000000000000..ac62a8b1655681eede0b8d4a904dd598d6bb7d9e
Binary files /dev/null and b/src/client/assets/room/furnitures/server/server.png differ
diff --git a/src/client/assets/room/furnitures/server/uv.png b/src/client/assets/room/furnitures/server/uv.png
new file mode 100644
index 0000000000000000000000000000000000000000..6c7e4950146a0b60f218e33f1bbc1faae1fd29d1
Binary files /dev/null and b/src/client/assets/room/furnitures/server/uv.png differ
diff --git a/src/client/assets/room/furnitures/tv/screen-uv.png b/src/client/assets/room/furnitures/tv/screen-uv.png
new file mode 100644
index 0000000000000000000000000000000000000000..c02e01c2e5bb1dbc804f93ffbdc04e1532e3bd32
Binary files /dev/null and b/src/client/assets/room/furnitures/tv/screen-uv.png differ
diff --git a/src/client/assets/room/furnitures/tv/tv.blend b/src/client/assets/room/furnitures/tv/tv.blend
new file mode 100644
index 0000000000000000000000000000000000000000..490e298e7b24dd1928fd4d39d7f54b7fdea63250
Binary files /dev/null and b/src/client/assets/room/furnitures/tv/tv.blend differ
diff --git a/src/client/assets/room/furnitures/tv/tv.glb b/src/client/assets/room/furnitures/tv/tv.glb
new file mode 100644
index 0000000000000000000000000000000000000000..b9bd23896b394fd59991885296acea68b6b37dcd
Binary files /dev/null and b/src/client/assets/room/furnitures/tv/tv.glb differ
diff --git a/src/client/assets/room/furnitures/wall-clock/wall-clock.blend b/src/client/assets/room/furnitures/wall-clock/wall-clock.blend
new file mode 100644
index 0000000000000000000000000000000000000000..0a61c8f01e3eab209e3a2ed73fbfc13feaaa84b7
Binary files /dev/null and b/src/client/assets/room/furnitures/wall-clock/wall-clock.blend differ
diff --git a/src/client/assets/room/furnitures/wall-clock/wall-clock.glb b/src/client/assets/room/furnitures/wall-clock/wall-clock.glb
new file mode 100644
index 0000000000000000000000000000000000000000..b9f0093a8dc470e2783a9a53b97a021e330ee037
Binary files /dev/null and b/src/client/assets/room/furnitures/wall-clock/wall-clock.glb differ
diff --git a/src/client/assets/room/rooms/default/default.blend b/src/client/assets/room/rooms/default/default.blend
new file mode 100644
index 0000000000000000000000000000000000000000..661154724aafc4b46db0d6100e0be6a9458335a7
Binary files /dev/null and b/src/client/assets/room/rooms/default/default.blend differ
diff --git a/src/client/assets/room/rooms/default/default.glb b/src/client/assets/room/rooms/default/default.glb
new file mode 100644
index 0000000000000000000000000000000000000000..3d378deee2a9c63add57a19753481a18e409c81a
Binary files /dev/null and b/src/client/assets/room/rooms/default/default.glb differ
diff --git a/src/client/assets/room/rooms/washitsu/husuma-uv.png b/src/client/assets/room/rooms/washitsu/husuma-uv.png
new file mode 100644
index 0000000000000000000000000000000000000000..3d0b08f488132b19c306ab54b2435c873fbe9197
Binary files /dev/null and b/src/client/assets/room/rooms/washitsu/husuma-uv.png differ
diff --git a/src/client/assets/room/rooms/washitsu/husuma.png b/src/client/assets/room/rooms/washitsu/husuma.png
new file mode 100644
index 0000000000000000000000000000000000000000..fd1f5ef23185d0a6b84cf3be8889cafd3f07d91f
Binary files /dev/null and b/src/client/assets/room/rooms/washitsu/husuma.png differ
diff --git a/src/client/assets/room/rooms/washitsu/tatami-single1600.png b/src/client/assets/room/rooms/washitsu/tatami-single1600.png
new file mode 100644
index 0000000000000000000000000000000000000000..49fd2d64763ec46768fa80e8ea77d05fe8d675f8
Binary files /dev/null and b/src/client/assets/room/rooms/washitsu/tatami-single1600.png differ
diff --git a/src/client/assets/room/rooms/washitsu/tatami-uv.png b/src/client/assets/room/rooms/washitsu/tatami-uv.png
new file mode 100644
index 0000000000000000000000000000000000000000..b4bba33850d8a4c698da3fa4d744841fd05a36ec
Binary files /dev/null and b/src/client/assets/room/rooms/washitsu/tatami-uv.png differ
diff --git a/src/client/assets/room/rooms/washitsu/tatami.afdesign b/src/client/assets/room/rooms/washitsu/tatami.afdesign
new file mode 100644
index 0000000000000000000000000000000000000000..9300a26950a1ab857fe71d41893c8eb2b5429ba1
Binary files /dev/null and b/src/client/assets/room/rooms/washitsu/tatami.afdesign differ
diff --git a/src/client/assets/room/rooms/washitsu/tatami.png b/src/client/assets/room/rooms/washitsu/tatami.png
new file mode 100644
index 0000000000000000000000000000000000000000..2137d4eca737c9aa22dc09c65192ca3f9cc39b91
Binary files /dev/null and b/src/client/assets/room/rooms/washitsu/tatami.png differ
diff --git a/src/client/assets/room/rooms/washitsu/washitsu.blend b/src/client/assets/room/rooms/washitsu/washitsu.blend
new file mode 100644
index 0000000000000000000000000000000000000000..84dc11374d63314bc95b4556ad0c588dc420cb98
Binary files /dev/null and b/src/client/assets/room/rooms/washitsu/washitsu.blend differ
diff --git a/src/client/assets/room/rooms/washitsu/washitsu.glb b/src/client/assets/room/rooms/washitsu/washitsu.glb
new file mode 100644
index 0000000000000000000000000000000000000000..5b4767bc73d5c27e677c6b3832e7900ecbeb7604
Binary files /dev/null and b/src/client/assets/room/rooms/washitsu/washitsu.glb differ
diff --git a/src/models/entities/user-profile.ts b/src/models/entities/user-profile.ts
index 61e80049c3fa46f2d7118182bbd413ad58979850..1244fa43902cbfb58700a0631d3d905352e56475 100644
--- a/src/models/entities/user-profile.ts
+++ b/src/models/entities/user-profile.ts
@@ -99,6 +99,12 @@ export class UserProfile {
 	})
 	public clientData: Record<string, any>;
 
+	@Column('jsonb', {
+		default: {},
+		comment: 'The room data of the User.'
+	})
+	public room: Record<string, any>;
+
 	@Column('boolean', {
 		default: false,
 	})
diff --git a/src/server/api/endpoints/room/show.ts b/src/server/api/endpoints/room/show.ts
new file mode 100644
index 0000000000000000000000000000000000000000..17634834acc008df0d921ae8ec9735a22d1226aa
--- /dev/null
+++ b/src/server/api/endpoints/room/show.ts
@@ -0,0 +1,92 @@
+import $ from 'cafy';
+import define from '../../define';
+import { ApiError } from '../../error';
+import { Users, UserProfiles } from '../../../../models';
+import { ID } from '../../../../misc/cafy-id';
+import { ensure } from '../../../../prelude/ensure';
+import { toPunyNullable } from '../../../../misc/convert-host';
+
+export const meta = {
+	desc: {
+		'ja-JP': '指定した部屋の情報を取得します。',
+	},
+
+	tags: ['room'],
+
+	requireCredential: false,
+
+	params: {
+		userId: {
+			validator: $.optional.type(ID),
+			desc: {
+				'ja-JP': '対象のユーザーのID',
+				'en-US': 'Target user ID'
+			}
+		},
+
+		username: {
+			validator: $.optional.str
+		},
+
+		host: {
+			validator: $.optional.nullable.str
+		},
+	},
+
+	errors: {
+		noSuchUser: {
+			message: 'No such user.',
+			code: 'NO_SUCH_USER',
+			id: '7ad3fa3e-5e12-42f0-b23a-f3d13f10ee4b'
+		}
+	}
+};
+
+export default define(meta, async (ps, me) => {
+	const user = await Users.findOne(ps.userId != null
+		? { id: ps.userId }
+		: { usernameLower: ps.username!.toLowerCase(), host: toPunyNullable(ps.host) });
+
+	if (user == null) {
+		throw new ApiError(meta.errors.noSuchUser);
+	}
+
+	const profile = await UserProfiles.findOne(user.id).then(ensure);
+
+	if (profile.room.furnitures == null) {
+		await UserProfiles.update({ userId: user.id }, {
+			room: {
+				furnitures: [],
+				...profile.room
+			}
+		});
+
+		profile.room.furnitures = [];
+	}
+
+	if (profile.room.roomType == null) {
+		const initialType = 'default';
+		await UserProfiles.update({ userId: user.id }, {
+			room: {
+				roomType: initialType as any,
+				...profile.room
+			}
+		});
+
+		profile.room.roomType = initialType;
+	}
+
+	if (profile.room.carpetColor == null) {
+		const initialColor = '#85CAF0';
+		await UserProfiles.update({ userId: user.id }, {
+			room: {
+				carpetColor: initialColor as any,
+				...profile.room
+			}
+		});
+
+		profile.room.carpetColor = initialColor;
+	}
+
+	return profile.room;
+});
diff --git a/src/server/api/endpoints/room/update.ts b/src/server/api/endpoints/room/update.ts
new file mode 100644
index 0000000000000000000000000000000000000000..897f65f2d25158cae4f54111aaa8b3f6b5d80077
--- /dev/null
+++ b/src/server/api/endpoints/room/update.ts
@@ -0,0 +1,48 @@
+import $ from 'cafy';
+import { publishMainStream } from '../../../../services/stream';
+import define from '../../define';
+import { Users, UserProfiles } from '../../../../models';
+
+export const meta = {
+	requireCredential: true,
+
+	params: {
+		room: {
+			validator: $.obj({
+				furnitures: $.arr($.obj({
+					id: $.str,
+					type: $.str,
+					position: $.obj({
+						x: $.num,
+						y: $.num,
+						z: $.num,
+					}),
+					rotation: $.obj({
+						x: $.num,
+						y: $.num,
+						z: $.num,
+					}),
+					props: $.optional.nullable.obj(),
+				})),
+				roomType: $.str,
+				carpetColor: $.str
+			})
+		},
+	},
+};
+
+export default define(meta, async (ps, user) => {
+	await UserProfiles.update({ userId: user.id }, {
+		room: ps.room as any
+	});
+
+	const iObj = await Users.pack(user.id, user, {
+		detail: true,
+		includeSecrets: true
+	});
+
+	// Publish meUpdated event
+	publishMainStream(user.id, 'meUpdated', iObj);
+
+	return iObj;
+});
diff --git a/tsconfig.json b/tsconfig.json
index 4f1d1b9cd560ccf4b5429aa4d190050637d387dc..62107507acdb3814f57b6c7f42a23fe3e1f5ab80 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -11,6 +11,7 @@
     "sourceMap": true,
     "target": "es2017",
     "module": "commonjs",
+    "moduleResolution": "node",
     "removeComments": false,
     "noLib": false,
     "strict": true,
diff --git a/yarn.lock b/yarn.lock
index 1ac9e145f23a27840031d43c736a37018a55f786..1d5bf3226c9ac1a1672c9ca2e43b644052fae4af 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -10994,6 +10994,11 @@ thenify-all@^1.0.0:
   dependencies:
     any-promise "^1.0.0"
 
+three@0.107.0:
+  version "0.107.0"
+  resolved "https://registry.yarnpkg.com/three/-/three-0.107.0.tgz#0c862c348d61bd3f22058e01f5e771c1294315a1"
+  integrity sha512-vqbKJRLBEviPVa7poEzXocobicwxzsctr5mnymA7n8fEzcVS49rYP0RrwqZ98JqujRoruK+/YzcchNpRP+kXsQ==
+
 through2-concurrent@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/through2-concurrent/-/through2-concurrent-2.0.0.tgz#c9dd2c146504ec9962dbc86a5168b63d662669fa"