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
部屋のデータ
今回は以下のデータを使用しました(私の住居のスキャンデータです)
部屋データ(長いので閉じてます)
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
}
表示
一部コード紹介(床生成)
上の画像では赤色の床が表示されていますが、RoomPlanでスキャンしたデータに床の情報はありません。
全ての壁のバウンディングボックスを取得し、それを使って床データを生成しています。
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コンポーネントに渡して表示しています。
...
<mesh
matrix={matrix}
matrixAutoUpdate={false}
>
<boxGeometry args={dimensions} />
<meshStandardMaterial color={FloorModel.Color} opacity={0.7} transparent={true} />
</mesh>
さいごに
開発環境がすぐ用意でき、開発体験もよく、three.jsが宣言的に書けるreact-three-fiber最高です。
3D開発モデルぐるぐるするだけでも楽しいのでぜひ触ってみてください
リモデラでは現在エンジニア募集をしています。この記事の内容に少しでも興味を持った方は採用サイトからのお問い合わせ、私のTwitterへのDMなどから気軽にメッセージを送っていただけると嬉しいです。