LoginSignup
0

react-three-fiberで自分の部屋を表示してみる

Last updated at Posted at 2023-01-31

Three.jsはWeb上で3D表現が気軽にでき、過去に何度か触れたことがあります。当時は、Vue.jsまたはReactで構築できるテンプレートが存在せず、環境を自分で構築するか、手続的に書いていくかのどちらかでした。

久々に調べてみるとreact-three-fiberという良さげなライブラリを発見しました。
これでRoomPlanでスキャンした部屋を表示するまでをやってみました。

RoomPlanについては以下の2記事もご参考にどうぞ。

環境構築

npx create-react-app your-app-name --template typescript
npm install three @react-three/fiber @react-three/drei
npm install --save-dev @types/three

部屋のデータ

今回は以下のデータを使用しました(私の住居のスキャンデータです)

部屋データ(長いので閉じてます)
data.ts
export const data = {
  "windows":[],
  "doors":[{"category":{"door":{"isOpen":false}},"confidence":{"high":{}},"dimensions":[0.70346218347549438,1.8716535568237305,0],"completedEdges":[],"parentIdentifier":"91C3F0A6-6696-41AB-967A-325D0798C327","identifier":"22798192-01B7-40D2-AA33-68CEE75821EC","curve":null,"transform":[0,0,1,0,0,1,0,0,-1,0,0,0,1.2999999523162842,-0.66180282831192017,-0.28146862983703613,1]},{"category":{"door":{"isOpen":false}},"confidence":{"high":{}},"dimensions":[0.60060214996337891,1.9556064605712891,0],"completedEdges":[],"parentIdentifier":"D67A37E8-1078-45ED-92AF-A0F9A7FA5CDB","identifier":"188AFD66-F873-4E5C-BFA8-231DB8F07D7E","curve":null,"transform":[1,0,0,0,0,1,0,0,0,0,1,0,0.78633666038513184,-0.61982637643814087,-0.69999998807907104,1]},{"category":{"door":{"isOpen":false}},"confidence":{"high":{}},"dimensions":[0.66166341304779053,2.1284506320953369,0],"completedEdges":[],"parentIdentifier":"B42B3B39-C7DE-43C4-8284-33B2415650D2","identifier":"DB968143-D22A-4B2E-949F-A9E5F9DC31E0","curve":null,"transform":[1,0,0,0,0,1,0,0,0,0,1,0,0.0036210163962095976,-0.53340440988540649,-2,1]},{"category":{"door":{"isOpen":false}},"confidence":{"high":{}},"dimensions":[0.71239167451858521,1.9506680965423584,0],"completedEdges":[],"parentIdentifier":"4F08BBA7-0F66-4390-97CD-7E78612611D3","identifier":"3AAAA40C-14CE-41DA-BFE8-E3686424A46C","curve":null,"transform":[0,0,-1,0,0,1,0,0,1,0,0,0,-0.60000002384185791,-0.62229567766189575,-0.29999998211860657,1]},{"category":{"door":{"isOpen":false}},"confidence":{"high":{}},"dimensions":[0.75842225551605225,1.7383166551589966,0],"completedEdges":[],"parentIdentifier":"1616E478-F2B6-4E90-A464-FF1396A2CC94","identifier":"663C376A-5CFC-4BB3-853F-359CEF5C36E1","curve":null,"transform":[-1,0,0,0,0,1,0,0,0,0,-1,0,0.96780824661254883,-0.72847127914428711,1.1000000238418579,1]},{"category":{"door":{"isOpen":true}},"confidence":{"high":{}},"dimensions":[0.66552311182022095,2.049436092376709,0],"completedEdges":[],"parentIdentifier":"D67A37E8-1078-45ED-92AF-A0F9A7FA5CDB","identifier":"1F73C1D0-C409-4BE6-ABA5-9A912A242EAB","curve":null,"transform":[1,0,0,0,0,1,0,0,0,0,1,0,0.084048442542552948,-0.57291167974472046,-0.69999998807907104,1]}],
  "walls":[{"category":{"wall":{}},"confidence":{"high":{}},"dimensions":[1.6999999284744263,2.2124032974243164,0],"completedEdges":[],"parentIdentifier":null,"identifier":"755A0F9A-5371-48E1-AEBC-F6055F211E07","curve":null,"transform":[1,0,0,0,0,1,0,0,0,0,1,0,0.44999998807907104,-0.49142807722091675,-3.2000000476837158,1]},{"category":{"wall":{}},"confidence":{"high":{}},"dimensions":[1.2000000476837158,2.2124032974243164,0],"completedEdges":[],"parentIdentifier":null,"identifier":"3D19BDDD-496B-4381-AEAF-4BD7EDCF5AC9","curve":null,"transform":[0,0,-1.0000002384185791,0,0,1,0,0,1.0000002384185791,0,0,0,-0.40000000596046448,-0.49142807722091675,-2.5999999046325684,1]},{"category":{"wall":{}},"confidence":{"high":{}},"dimensions":[1.1000000238418579,2.2124032974243164,0],"completedEdges":[],"parentIdentifier":null,"identifier":"86C0543F-BBD5-4E6C-9D67-3C43202E68EB","curve":null,"transform":[-1,0,0,0,0,1,0,0,0,0,-1,0,-0.050000011920928955,-0.49142807722091675,0.10000000149011612,1]},{"category":{"wall":{}},"confidence":{"high":{}},"dimensions":[1.2999999523162842,2.2124032974243164,0],"completedEdges":[],"parentIdentifier":null,"identifier":"59B6880F-2BF2-440F-9A6A-6F71A779086D","curve":null,"transform":[0,0,-1,0,0,1,0,0,1,0,0,0,-1.1000000238418579,-0.49142807722091675,-1.3500000238418579,1]},{"category":{"wall":{}},"confidence":{"high":{}},"dimensions":[0.70000004768371582,2.2124032974243164,0],"completedEdges":[],"parentIdentifier":null,"identifier":"9401651F-F32D-47FB-87D5-A0403152562C","curve":null,"transform":[0.99999994039535522,0,0,0,0,1,0,0,0,0,0.99999994039535522,0,-0.75,-0.49142807722091675,-2,1]},{"category":{"wall":{}},"confidence":{"high":{}},"dimensions":[1,2.2124032974243164,0],"completedEdges":[],"parentIdentifier":null,"identifier":"096AD339-9428-4FA7-8072-EBC52279982F","curve":null,"transform":[0,0,1,0,0,1,0,0,-1,0,0,0,0.5,-0.49142807722091675,0.60000002384185791,1]},{"category":{"wall":{}},"confidence":{"high":{}},"dimensions":[0.89999997615814209,2.2124032974243164,0],"completedEdges":[],"parentIdentifier":null,"identifier":"B42B3B39-C7DE-43C4-8284-33B2415650D2","curve":null,"transform":[1,0,0,0,0,1,0,0,0,0,1,0,0.049999997019767761,-0.49142807722091675,-2,1]},{"category":{"wall":{}},"confidence":{"high":{}},"dimensions":[0.60000002384185791,2.2124032974243164,0],"completedEdges":[],"parentIdentifier":null,"identifier":"DAA835BE-90F4-4A44-B411-5180A2F5AD5C","curve":null,"transform":[0,0,1.0000002384185791,0,0,1,0,0,-1.0000002384185791,0,0,0,0.5,-0.49142807722091675,-1.7000000476837158,1]},{"category":{"wall":{}},"confidence":{"high":{}},"dimensions":[0.80000001192092896,2.2124032974243164,0],"completedEdges":[],"parentIdentifier":null,"identifier":"4F08BBA7-0F66-4390-97CD-7E78612611D3","curve":null,"transform":[0,0,-1,0,0,1,0,0,1,0,0,0,-0.60000002384185791,-0.49142807722091675,-0.29999998211860657,1]},{"category":{"wall":{}},"confidence":{"high":{}},"dimensions":[0.5,2.2124032974243164,0],"completedEdges":[],"parentIdentifier":null,"identifier":"46FA06C5-56F0-4BCD-A700-C181D48BDCD4","curve":null,"transform":[-1,0,0,0,0,1,0,0,0,0,-1,0,-0.85000002384185791,-0.49142807722091675,-0.69999998807907104,1]},{"category":{"wall":{}},"confidence":{"high":{}},"dimensions":[0.79999995231628418,2.2124032974243164,0],"completedEdges":[],"parentIdentifier":null,"identifier":"1616E478-F2B6-4E90-A464-FF1396A2CC94","curve":null,"transform":[-1,0,0,0,0,1,0,0,0,0,-1,0,0.89999997615814209,-0.49142807722091675,1.1000000238418579,1]},{"category":{"wall":{}},"confidence":{"high":{}},"dimensions":[1.7999999523162842,2.2124032974243164,0],"completedEdges":[],"parentIdentifier":null,"identifier":"91C3F0A6-6696-41AB-967A-325D0798C327","curve":null,"transform":[0,0,1,0,0,1,0,0,-1,0,0,0,1.2999999523162842,-0.49142807722091675,0.20000001788139343,1]},{"category":{"wall":{}},"confidence":{"high":{}},"dimensions":[1.2999999523162842,2.2124032974243164,0],"completedEdges":[],"parentIdentifier":null,"identifier":"16CD9E81-7B6E-4761-BC93-2FF423FAE7E7","curve":null,"transform":[0,0,1.0000001192092896,0,0,1,0,0,-1.0000001192092896,0,0,0,1.2999999523162842,-0.49142807722091675,-1.3500000238418579,1]},{"category":{"wall":{}},"confidence":{"high":{}},"dimensions":[1.2000000476837158,2.2124032974243164,0],"completedEdges":[],"parentIdentifier":null,"identifier":"ADC1886C-BA32-4580-9CBC-0D4D533C1DC3","curve":null,"transform":[0,0,0.99999982118606567,0,0,1,0,0,-0.99999982118606567,0,0,0,1.2999999523162842,-0.49142807722091675,-2.5999999046325684,1]},{"category":{"wall":{}},"confidence":{"high":{}},"dimensions":[0.79999995231628418,2.2124032974243164,0],"completedEdges":[],"parentIdentifier":null,"identifier":"092DDAF9-A950-4EB0-9B86-9A20F428B20F","curve":null,"transform":[1,0,0,0,0,1,0,0,0,0,1,0,0.89999997615814209,-0.49142807722091675,-2,1]},{"category":{"wall":{}},"confidence":{"high":{}},"dimensions":[1.8999999761581421,2.2124032974243164,0],"completedEdges":[],"parentIdentifier":null,"identifier":"D67A37E8-1078-45ED-92AF-A0F9A7FA5CDB","curve":null,"transform":[1,0,0,0,0,1,0,0,0,0,1,0,0.34999996423721313,-0.49142807722091675,-0.69999998807907104,1]}],
  "openings":[],
  "objects":[
    {
      "dimensions":[0.4962867796421051,0.85711753368377686,0.8464047908782959],
      "transform":[0.99992614984512329,0,0.012144289910793304,0,0,1,0,0,-0.012144260108470917,0,0.99992614984512329,0,0.9520525336265564,-1.1690709590911865,-1.6141649484634399,1],
      "identifier":"2450FA76-8D2A-4348-8235-6DED39B535EB","category":{"toilet":{}},"confidence":{"high":{}}
    },
    {
      "dimensions":[0.48041149973869324,0.30000007152557373,0.3875267505645752],
      "transform":[0.99908584356307983,0,-0.042745564132928848,0,0,1.0000001192092896,0,0,0.042745586484670639,0,0.99908602237701416,0,-0.74199879169464111,-0.7986028790473938,-1.8306409120559692,1],
      "identifier":"FAF093D7-2114-46C5-8FC6-EC47C689C1D8","category":{"sink":{}},"confidence":{"medium":{}}
    },
    {
      "dimensions":[1.1133368015289307,0.51811295747756958,0.82873803377151489],
      "transform":[0.025234300643205643,0,0.99968159198760986,0,0,0.99999994039535522,0,0,-0.99968153238296509,0,0.025234298780560493,0,0.90238362550735474,-1.3385732173919678,-2.6174006462097168,1],
      "identifier":"BDEA9FCE-4A74-4185-932D-4399565A8250","category":{"bathtub":{}},"confidence":{"high":{}}
    }
  ],
  "version":1
}

表示

スクリーンショット 2023-02-01 1.18.50.png

一部コード紹介(床生成)

上の画像では赤色の床が表示されていますが、RoomPlanでスキャンしたデータに床の情報はありません。
全ての壁のバウンディングボックスを取得し、それを使って床データを生成しています。

WallsModel.ts
export default class WallsModel {
  public walls: WallModel[]

  constructor(
    walls: any[],
  ) {
    this.walls = walls.map(v => new WallModel(v))
  }

  get boundingBox(): BoundingBoxModel {
    const boundingBox = {
      min: { x: Infinity, y: Infinity, z: Infinity },
      max: { x:-Infinity, y:-Infinity, z:-Infinity },
    }
    const allPoints: any[] = []
    for (const wall of this.walls) {
      const bb = wall.createBoundinBox()
      allPoints.push(bb.min)
      allPoints.push(bb.max)
    }
    for (const point of allPoints) {
      boundingBox.min.x = Math.min(boundingBox.min.x, point.x)
      boundingBox.min.y = Math.min(boundingBox.min.y, point.y)
      boundingBox.min.z = Math.min(boundingBox.min.z, point.z)

      boundingBox.max.x = Math.max(boundingBox.max.x, point.x)
      boundingBox.max.y = Math.max(boundingBox.max.y, point.y)
      boundingBox.max.z = Math.max(boundingBox.max.z, point.z)
    }
    return new BoundingBoxModel(boundingBox.min, boundingBox.max)
  }
  ...

そのBoundingBoxからdimensions, transformを生成。
Floorコンポーネントに渡して表示しています。

Floor.tsx
    ...
    <mesh
      matrix={matrix}
      matrixAutoUpdate={false} 
    >
      <boxGeometry args={dimensions} />
      <meshStandardMaterial color={FloorModel.Color} opacity={0.7} transparent={true} />
    </mesh>

さいごに

開発環境がすぐ用意でき、開発体験もよく、three.jsが宣言的に書けるreact-three-fiber最高です。
3D開発モデルぐるぐるするだけでも楽しいのでぜひ触ってみてください:cat:

リモデラでは現在エンジニア募集をしています。この記事の内容に少しでも興味を持った方は採用サイトからのお問い合わせ、私のTwitterへのDMなどから気軽にメッセージを送っていただけると嬉しいです。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0