diff --git a/src/Editor.tsx b/src/Editor.tsx index 209eae7..8356f74 100644 --- a/src/Editor.tsx +++ b/src/Editor.tsx @@ -2,8 +2,6 @@ import { OrbitControls, Stats } from "drei"; import React from "react"; import { Canvas } from "react-three-fiber"; import { DoubleSide, PCFSoftShadowMap, Uncharted2ToneMapping } from "three"; -import Building from "./building/Building"; -import InfoPanel from "./info/InfoPanel"; import RectangularGrid from "./shared/RectangularGrid"; import { useStore } from "./shared/store"; @@ -57,7 +55,7 @@ const Editor: React.FC = () => ( /> - + {/* */} ( /> {process.env.REACT_APP_DEBUG && } - ); diff --git a/src/building/Building.tsx b/src/building/Building.tsx index 5c3b11c..f2bacce 100644 --- a/src/building/Building.tsx +++ b/src/building/Building.tsx @@ -1,37 +1,18 @@ -import React, { useState } from "react"; -import { State, useStore } from "../shared/store"; -import Module from "./Module"; +import React from "react"; +import { useStore } from "../shared/store"; +import { current } from "../utils/undoable"; -const Building: React.FC = () => { - const [hovering, setHovering] = useState(undefined); - - const { - properties: { - dimensions: { cellWidth, cellLength }, - }, - occupiedCells, - } = useStore((store) => store.grid); +const Building: React.FC<{ index: number }> = ({ index }) => { + // get the first coordinate of the hangar to determine the building's position + const { x, z } = useStore((store) => current(store.hangars)[index][0]); return ( - <> - - {Object.entries(occupiedCells as State["grid"]["occupiedCells"]).map( - ([gridPosition, cellData]) => ( - - ) - )} - - {hovering && ( - - )} - + + + + + + ); }; diff --git a/src/hangar/index.tsx b/src/hangar/index.tsx index 21869c5..ec5870b 100644 --- a/src/hangar/index.tsx +++ b/src/hangar/index.tsx @@ -2,6 +2,7 @@ import { OrbitControls } from "drei"; import React from "react"; import { Canvas, CanvasContext, PointerEvent } from "react-three-fiber"; import * as three from "three"; +import Building from "../building/Building"; import grid from "../shared/grid"; import RectangularGrid from "../shared/RectangularGrid"; import { @@ -49,9 +50,7 @@ const matchingIndices = ( ); }; -const { x: gridX, z: gridZ } = grid("m"); - -const gridY = 4; +const { x: gridX, z: gridZ, y: gridY } = grid("mm"); const snapToGridX = (val: number): number => Math.round(val / gridX) * gridX; const snapToGridZ = (val: number): number => Math.round(val / gridZ) * gridZ; @@ -432,18 +431,19 @@ const Container: React.FunctionComponent<{}> = () => { onEditModeChange={setEditMode} /> { setThreeContext(threeContext); threeContext.gl.toneMapping = three.Uncharted2ToneMapping; - threeContext.camera.position.set(50, 70, -24); + threeContext.camera.position.set(-13000, 20000, -15000); threeContext.camera.lookAt(0, 0, 0); threeContext.camera.updateProjectionMatrix(); + window["camera"] = threeContext.camera; }} camera={{ near: 1, - far: 120, - zoom: 40, + far: 12e5, + zoom: 0.035, }} orthographic {...dragContainerAttrs} @@ -457,19 +457,24 @@ const Container: React.FunctionComponent<{}> = () => { numXCells={60} cellLength={gridZ} cellWidth={gridX} - color="#F3F3F3" + color={0xcccccc} /> + + + {editMode === EditMode.Insert && ghostHangar && ( )} @@ -488,6 +493,8 @@ const Container: React.FunctionComponent<{}> = () => { return ( + + = () => { ); })} + ); diff --git a/src/info/InfoPanel.tsx b/src/info/InfoPanel.tsx deleted file mode 100644 index bfda9a2..0000000 --- a/src/info/InfoPanel.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import React from "react"; -import crossSections from "../building/crossSections"; -import { State, useStore } from "../shared/store"; -import chassisData from "./data/chassis.json"; -import styles from "./infoPanel.module.css"; - -// TODO: pre-calculate and store all module costs etc in main config file - -const ModuleSelector: React.FC<{ selected: string; position: string }> = ({ - selected, - position, -}) => { - const set = useStore((store) => store.set); - const handleChange = (e) => - set((state: State) => { - state.grid.occupiedCells[position].module = e.target.value; - }); - - return ( - - ); -}; - -const InfoPanel: React.FC = () => { - const occupiedCells = useStore( - (state) => state.grid.occupiedCells - ) as State["grid"]["occupiedCells"]; - - return ( - - - - - - - - - - - {Object.entries(occupiedCells).map(([position, data], i) => ( - - - - - - ))} - - - - - - - - -
Bay #ModuleCost (€)
{i + 1} - - - {chassisData?.find((x) => - x["Module Name"].endsWith(data.module) - )?.["Structure cost (Euro)"] || "?"} -
- Total -
- ); -}; - -export default InfoPanel; diff --git a/src/info/data/chassis.csv b/src/info/data/chassis.csv deleted file mode 100644 index 56b8af2..0000000 --- a/src/info/data/chassis.csv +++ /dev/null @@ -1,13 +0,0 @@ -Module Name,Class,X Grid Dim (m),Y Grid Dim (m),Z Grid Dim (m),Roof pitch type,Structure cost (Euro),Gross Internal Area,No. of storeys,Footprint Area (m2),External wall area (m2),External roof area (m2),Internal wall area (m2),Internal roof area (m2),Floor thickness (m),Wall thickness (m),Roof thickness (m),Insulation Volume (m3) -Swift_A1_05,Module,2.4,1.2,5.169,Dual-centre,€695.00,2.88,1,3.69,9.69,5.21,8.55,4.07,0.336,0.336,0.336,5.724 -Swift_A1_04,Module,1.2,1.2,4.137,Mono,€691.00,1.44,1,2.25,9.06,3.18,7.11,2.04,0.336,0.336,0.336, -Swift_A1_03,Module,2.4,1.2,5.169,Mono,€760.00,2.88,1,3.69,9.69,5.21,8.55,4.07,0.336,0.336,0.336, -Swift_A1_02,Module,3.6,1.2,5.169,Asymetric,€792.00,4.32,1,5.13,8.25,7.25,7.11,6.11,0.336,0.336,0.336, -Swift_A1_01,Module,4.8,1.2,5.169,Dual-centre,€819.00,5.76,1,6.57,6.81,9.29,5.67,8.15,0.336,0.336,0.336,7.092 -Swift_A1_v/a,Gevel (Facade),,,,Dual-centre,€410.00,0,1,0,,,,,0,0.336,0, -Swift_A2_05,Module,2.4,1.2,8.169,Dual-centre,"€1,059.00",5.76,2,3.69,16.89,5.21,14.95,4.07,0.336,0.336,0.336, -Swift_A2_04,Module,1.2,1.2,7.137,Mono,€885.00,2.88,2,2.25,16.26,3.18,13.51,2.04,0.336,0.336,0.336, -Swift_A2_03,Module,2.4,1.2,8.169,Mono,"€1,050.00",5.76,2,3.69,16.89,5.21,14.95,4.07,0.336,0.336,0.336, -Swift_A2_02,Module,3.6,1.2,8.169,Asymetric,"€1,077.00",8.64,2,5.13,15.45,7.25,13.51,6.11,0.336,0.336,0.336, -Swift_A2_01,Module,4.8,1.2,8.169,Dual-centre,"€1,106.00",11.52,2,6.57,14.01,9.29,12.07,8.15,0.336,0.336,0.336, -Swift_A2_v/a,Gevel (Facade),,,,Dual-centre,€553.00,0,2,0,,,,,0,0.336,0, diff --git a/src/info/data/chassis.json b/src/info/data/chassis.json deleted file mode 100644 index 2d3cd93..0000000 --- a/src/info/data/chassis.json +++ /dev/null @@ -1,242 +0,0 @@ -[ - { - "Module Name": "Swift_A1_05", - "Class": "Module", - "X Grid Dim (m)": 2.4, - "Y Grid Dim (m)": 1.2, - "Z Grid Dim (m)": 5.169, - "Roof pitch type": "Dual-centre", - "Structure cost (Euro)": "€695.00", - "Gross Internal Area": 2.88, - "No. of storeys": 1, - "Footprint Area (m2)": 3.69, - "External wall area (m2)": 9.69, - "External roof area (m2)": 5.21, - "Internal wall area (m2)": 8.55, - "Internal roof area (m2)": 4.07, - "Floor thickness (m)": 0.336, - "Wall thickness (m)": 0.336, - "Roof thickness (m)": 0.336, - "Insulation Volume (m3)": 5.724 - }, - { - "Module Name": "Swift_A1_04", - "Class": "Module", - "X Grid Dim (m)": 1.2, - "Y Grid Dim (m)": 1.2, - "Z Grid Dim (m)": 4.137, - "Roof pitch type": "Mono", - "Structure cost (Euro)": "€691.00", - "Gross Internal Area": 1.44, - "No. of storeys": 1, - "Footprint Area (m2)": 2.25, - "External wall area (m2)": 9.06, - "External roof area (m2)": 3.18, - "Internal wall area (m2)": 7.11, - "Internal roof area (m2)": 2.04, - "Floor thickness (m)": 0.336, - "Wall thickness (m)": 0.336, - "Roof thickness (m)": 0.336, - "Insulation Volume (m3)": "" - }, - { - "Module Name": "Swift_A1_03", - "Class": "Module", - "X Grid Dim (m)": 2.4, - "Y Grid Dim (m)": 1.2, - "Z Grid Dim (m)": 5.169, - "Roof pitch type": "Mono", - "Structure cost (Euro)": "€760.00", - "Gross Internal Area": 2.88, - "No. of storeys": 1, - "Footprint Area (m2)": 3.69, - "External wall area (m2)": 9.69, - "External roof area (m2)": 5.21, - "Internal wall area (m2)": 8.55, - "Internal roof area (m2)": 4.07, - "Floor thickness (m)": 0.336, - "Wall thickness (m)": 0.336, - "Roof thickness (m)": 0.336, - "Insulation Volume (m3)": "" - }, - { - "Module Name": "Swift_A1_02", - "Class": "Module", - "X Grid Dim (m)": 3.6, - "Y Grid Dim (m)": 1.2, - "Z Grid Dim (m)": 5.169, - "Roof pitch type": "Asymetric", - "Structure cost (Euro)": "€792.00", - "Gross Internal Area": 4.32, - "No. of storeys": 1, - "Footprint Area (m2)": 5.13, - "External wall area (m2)": 8.25, - "External roof area (m2)": 7.25, - "Internal wall area (m2)": 7.11, - "Internal roof area (m2)": 6.11, - "Floor thickness (m)": 0.336, - "Wall thickness (m)": 0.336, - "Roof thickness (m)": 0.336, - "Insulation Volume (m3)": "" - }, - { - "Module Name": "Swift_A1_01", - "Class": "Module", - "X Grid Dim (m)": 4.8, - "Y Grid Dim (m)": 1.2, - "Z Grid Dim (m)": 5.169, - "Roof pitch type": "Dual-centre", - "Structure cost (Euro)": "€819.00", - "Gross Internal Area": 5.76, - "No. of storeys": 1, - "Footprint Area (m2)": 6.57, - "External wall area (m2)": 6.81, - "External roof area (m2)": 9.29, - "Internal wall area (m2)": 5.67, - "Internal roof area (m2)": 8.15, - "Floor thickness (m)": 0.336, - "Wall thickness (m)": 0.336, - "Roof thickness (m)": 0.336, - "Insulation Volume (m3)": 7.092 - }, - { - "Module Name": "Swift_A1_v/a", - "Class": "Gevel (Facade)", - "X Grid Dim (m)": "", - "Y Grid Dim (m)": "", - "Z Grid Dim (m)": "", - "Roof pitch type": "Dual-centre", - "Structure cost (Euro)": "€410.00", - "Gross Internal Area": 0, - "No. of storeys": 1, - "Footprint Area (m2)": 0, - "External wall area (m2)": "", - "External roof area (m2)": "", - "Internal wall area (m2)": "", - "Internal roof area (m2)": "", - "Floor thickness (m)": 0, - "Wall thickness (m)": 0.336, - "Roof thickness (m)": 0, - "Insulation Volume (m3)": "" - }, - { - "Module Name": "Swift_A2_05", - "Class": "Module", - "X Grid Dim (m)": 2.4, - "Y Grid Dim (m)": 1.2, - "Z Grid Dim (m)": 8.169, - "Roof pitch type": "Dual-centre", - "Structure cost (Euro)": "€1,059.00", - "Gross Internal Area": 5.76, - "No. of storeys": 2, - "Footprint Area (m2)": 3.69, - "External wall area (m2)": 16.89, - "External roof area (m2)": 5.21, - "Internal wall area (m2)": 14.95, - "Internal roof area (m2)": 4.07, - "Floor thickness (m)": 0.336, - "Wall thickness (m)": 0.336, - "Roof thickness (m)": 0.336, - "Insulation Volume (m3)": "" - }, - { - "Module Name": "Swift_A2_04", - "Class": "Module", - "X Grid Dim (m)": 1.2, - "Y Grid Dim (m)": 1.2, - "Z Grid Dim (m)": 7.137, - "Roof pitch type": "Mono", - "Structure cost (Euro)": "€885.00", - "Gross Internal Area": 2.88, - "No. of storeys": 2, - "Footprint Area (m2)": 2.25, - "External wall area (m2)": 16.26, - "External roof area (m2)": 3.18, - "Internal wall area (m2)": 13.51, - "Internal roof area (m2)": 2.04, - "Floor thickness (m)": 0.336, - "Wall thickness (m)": 0.336, - "Roof thickness (m)": 0.336, - "Insulation Volume (m3)": "" - }, - { - "Module Name": "Swift_A2_03", - "Class": "Module", - "X Grid Dim (m)": 2.4, - "Y Grid Dim (m)": 1.2, - "Z Grid Dim (m)": 8.169, - "Roof pitch type": "Mono", - "Structure cost (Euro)": "€1,050.00", - "Gross Internal Area": 5.76, - "No. of storeys": 2, - "Footprint Area (m2)": 3.69, - "External wall area (m2)": 16.89, - "External roof area (m2)": 5.21, - "Internal wall area (m2)": 14.95, - "Internal roof area (m2)": 4.07, - "Floor thickness (m)": 0.336, - "Wall thickness (m)": 0.336, - "Roof thickness (m)": 0.336, - "Insulation Volume (m3)": "" - }, - { - "Module Name": "Swift_A2_02", - "Class": "Module", - "X Grid Dim (m)": 3.6, - "Y Grid Dim (m)": 1.2, - "Z Grid Dim (m)": 8.169, - "Roof pitch type": "Asymetric", - "Structure cost (Euro)": "€1,077.00", - "Gross Internal Area": 8.64, - "No. of storeys": 2, - "Footprint Area (m2)": 5.13, - "External wall area (m2)": 15.45, - "External roof area (m2)": 7.25, - "Internal wall area (m2)": 13.51, - "Internal roof area (m2)": 6.11, - "Floor thickness (m)": 0.336, - "Wall thickness (m)": 0.336, - "Roof thickness (m)": 0.336, - "Insulation Volume (m3)": "" - }, - { - "Module Name": "Swift_A2_01", - "Class": "Module", - "X Grid Dim (m)": 4.8, - "Y Grid Dim (m)": 1.2, - "Z Grid Dim (m)": 8.169, - "Roof pitch type": "Dual-centre", - "Structure cost (Euro)": "€1,106.00", - "Gross Internal Area": 11.52, - "No. of storeys": 2, - "Footprint Area (m2)": 6.57, - "External wall area (m2)": 14.01, - "External roof area (m2)": 9.29, - "Internal wall area (m2)": 12.07, - "Internal roof area (m2)": 8.15, - "Floor thickness (m)": 0.336, - "Wall thickness (m)": 0.336, - "Roof thickness (m)": 0.336, - "Insulation Volume (m3)": "" - }, - { - "Module Name": "Swift_A2_v/a", - "Class": "Gevel (Facade)", - "X Grid Dim (m)": "", - "Y Grid Dim (m)": "", - "Z Grid Dim (m)": "", - "Roof pitch type": "Dual-centre", - "Structure cost (Euro)": "€553.00", - "Gross Internal Area": 0, - "No. of storeys": 2, - "Footprint Area (m2)": 0, - "External wall area (m2)": "", - "External roof area (m2)": "", - "Internal wall area (m2)": "", - "Internal roof area (m2)": "", - "Floor thickness (m)": 0, - "Wall thickness (m)": 0.336, - "Roof thickness (m)": 0, - "Insulation Volume (m3)": "" - } -] diff --git a/src/info/infoPanel.module.css b/src/info/infoPanel.module.css deleted file mode 100644 index 23ce2a6..0000000 --- a/src/info/infoPanel.module.css +++ /dev/null @@ -1,22 +0,0 @@ -.table { - position: fixed; - bottom: 10px; - right: 10px; - border-collapse: collapse; - z-index: 2; - font-size: 13px; -} - -.table th { - padding: 5px; - text-align: left; -} - -.total { - border-top: 1px solid black; -} - -.cost { - text-align: right; - font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; -} diff --git a/src/shared/grid.ts b/src/shared/grid.ts index d2231ad..a80120d 100644 --- a/src/shared/grid.ts +++ b/src/shared/grid.ts @@ -1,4 +1,4 @@ -const [WIDTH_MM, LENGTH_MM] = [5700, 1200]; +const [WIDTH_MM, HEIGHT_MM, LENGTH_MM] = [5700, 3500, 1200]; type Unit = "mm" | "cm" | "m"; @@ -17,6 +17,7 @@ const grid = (units: Unit = "mm") => { return { units, x: WIDTH_MM / divisor, + y: HEIGHT_MM / divisor, z: LENGTH_MM / divisor, }; }; diff --git a/src/shared/store.ts b/src/shared/store.ts index c4da622..7caab38 100644 --- a/src/shared/store.ts +++ b/src/shared/store.ts @@ -1,12 +1,9 @@ -import { Howl } from "howler"; import produce from "immer"; -import { uniq } from "ramda"; import create from "zustand"; -import { nextOddInt } from "../utils"; import * as undoable from "../utils/undoable"; import grid from "./grid"; -const GRID = grid("m"); +const GRID = grid("mm"); type Point = { x: number; z: number }; @@ -53,112 +50,29 @@ export interface State { numZCells?: number; }; }; - occupiedCells: Record< - string, - { - module?: string; - rotation?: number; - } - >; }; } -/* - * calculates size of grid so that there is always a 'padding' - * of a single grid cell surrounding all of the occupied cells - */ -const calcSize = (x, i) => { - const numbers = uniq(Object.keys(x).map((k) => Number(k.split(",")[i]))); - const largestAbsoluteNumber = Math.max.apply(null, numbers.map(Math.abs)); - - return nextOddInt(Math.max(numbers.length, largestAbsoluteNumber * 2) + 2); -}; - -/* - * wraps the store, writes static outputs to the store iself if dependencies - * change, effectively caching the results - */ -const selectorMiddleware = (config) => (set, get, api) => - config( - (args) => { - const previousState = get().grid.occupiedCells; - - set(args); - - const currentState = get().grid.occupiedCells; - - if (JSON.stringify(previousState) !== JSON.stringify(currentState)) { - set((state: State) => { - state.grid.properties.dimensions.numXCells = calcSize( - currentState, - 0 - ); - state.grid.properties.dimensions.numZCells = calcSize( - currentState, - 1 - ); - }); - } - }, - get, - api - ); - -export const [useStore, api] = create( - selectorMiddleware((set): State & { set: any } => ({ - grid: { - properties: { - color: "lightgray", - dimensions: { - cellWidth: GRID.x, - cellLength: GRID.z, - }, +export const [useStore, api] = create((set): State & { set: any } => ({ + grid: { + properties: { + color: "lightgray", + dimensions: { + cellWidth: GRID.x, + cellLength: GRID.z, }, - occupiedCells: {}, }, - editMode: EditMode.Move, - hangars: undoable.create([ - [ - { x: 0, z: 0 }, - { x: GRID.x, z: 0 }, - { x: GRID.x, z: GRID.z }, - { x: 0, z: GRID.z }, - ], - ]), - set: (fn) => set(produce(fn)), - })) -); - -// set the grid initial grid value AFTER creating the store -// so that the data gets processed by the selector middleware - -api.getState().set((state: State) => { - // try to load existing state from localStorage, otherwise use object below - state.grid.occupiedCells = JSON?.parse(localStorage.getItem("cache")) || { - "0,-1": { - module: "A2_01", - }, - "0,0": { - module: "B2_01", - }, - "0,1": { - module: "C2_01", - }, - }; -}); + }, + editMode: EditMode.Move, + hangars: undoable.create([ + [ + { x: 0, z: 0 }, + { x: GRID.x, z: 0 }, + { x: GRID.x, z: GRID.z }, + { x: 0, z: GRID.z }, + ], + ]), + set: (fn) => set(produce(fn)), +})); window["api"] = api; - -// rudimentarily save state to localStorage if grid.occupiedCells changes - -const popSoundEffect = new Howl({ - src: ["sounds/260614__kwahmah-02__pop.wav"], -}); - -api.subscribe( - (occupiedCells: State) => { - localStorage.setItem("cache", JSON.stringify(occupiedCells)); - popSoundEffect.play(); - }, - (state) => state.grid.occupiedCells -);