mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-20 15:36:18 +00:00
Revert "Merge pull request #20 from simoleo89/feature/floorplan-realtime-preview"
This reverts commit323c45518e, reversing changes made tod1a5996268.
This commit is contained in:
@@ -8,25 +8,13 @@ interface IFloorplanEditorContext
|
|||||||
setOriginalFloorplanSettings: Dispatch<SetStateAction<IFloorplanSettings>>;
|
setOriginalFloorplanSettings: Dispatch<SetStateAction<IFloorplanSettings>>;
|
||||||
visualizationSettings: IVisualizationSettings;
|
visualizationSettings: IVisualizationSettings;
|
||||||
setVisualizationSettings: Dispatch<SetStateAction<IVisualizationSettings>>;
|
setVisualizationSettings: Dispatch<SetStateAction<IVisualizationSettings>>;
|
||||||
floorHeight: number;
|
|
||||||
setFloorHeight: Dispatch<SetStateAction<number>>;
|
|
||||||
floorAction: number;
|
|
||||||
setFloorAction: Dispatch<SetStateAction<number>>;
|
|
||||||
tilemapVersion: number;
|
|
||||||
areaInfo: { total: number; walkable: number };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const FloorplanEditorContext = createContext<IFloorplanEditorContext>({
|
const FloorplanEditorContext = createContext<IFloorplanEditorContext>({
|
||||||
originalFloorplanSettings: null,
|
originalFloorplanSettings: null,
|
||||||
setOriginalFloorplanSettings: null,
|
setOriginalFloorplanSettings: null,
|
||||||
visualizationSettings: null,
|
visualizationSettings: null,
|
||||||
setVisualizationSettings: null,
|
setVisualizationSettings: null
|
||||||
floorHeight: 0,
|
|
||||||
setFloorHeight: null,
|
|
||||||
floorAction: 3,
|
|
||||||
setFloorAction: null,
|
|
||||||
tilemapVersion: 0,
|
|
||||||
areaInfo: { total: 0, walkable: 0 }
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const FloorplanEditorContextProvider: FC<ProviderProps<IFloorplanEditorContext>> = props => <FloorplanEditorContext.Provider { ...props } />;
|
export const FloorplanEditorContextProvider: FC<ProviderProps<IFloorplanEditorContext>> = props => <FloorplanEditorContext.Provider { ...props } />;
|
||||||
|
|||||||
@@ -1,22 +1,19 @@
|
|||||||
import { AddLinkEventTracker, FloorHeightMapEvent, ILinkEventTracker, RemoveLinkEventTracker, RoomEngineEvent, RoomVisualizationSettingsEvent, UpdateFloorPropertiesMessageComposer } from '@nitrots/nitro-renderer';
|
import { AddLinkEventTracker, FloorHeightMapEvent, ILinkEventTracker, RemoveLinkEventTracker, RoomEngineEvent, RoomVisualizationSettingsEvent, UpdateFloorPropertiesMessageComposer } from '@nitrots/nitro-renderer';
|
||||||
import { FC, useCallback, useEffect, useState } from 'react';
|
import { FC, useEffect, useState } from 'react';
|
||||||
import { FaCaretLeft, FaCaretRight } from 'react-icons/fa';
|
|
||||||
import { LocalizeText, SendMessageComposer } from '../../api';
|
import { LocalizeText, SendMessageComposer } from '../../api';
|
||||||
import { Button, ButtonGroup, Column, Flex, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../common';
|
import { Button, ButtonGroup, Flex, NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../common';
|
||||||
import { useMessageEvent, useNitroEvent } from '../../hooks';
|
import { useMessageEvent, useNitroEvent } from '../../hooks';
|
||||||
import { FloorplanEditorContextProvider } from './FloorplanEditorContext';
|
import { FloorplanEditorContextProvider } from './FloorplanEditorContext';
|
||||||
import { FloorplanEditor } from '@nitrots/nitro-renderer';
|
import { FloorplanEditor } from '@nitrots/nitro-renderer';
|
||||||
import { IFloorplanSettings } from '@nitrots/nitro-renderer';
|
import { IFloorplanSettings } from '@nitrots/nitro-renderer';
|
||||||
import { IVisualizationSettings } from '@nitrots/nitro-renderer';
|
import { IVisualizationSettings } from '@nitrots/nitro-renderer';
|
||||||
import { convertNumbersForSaving, convertSettingToNumber, FloorAction, HEIGHT_SCHEME } from '@nitrots/nitro-renderer';
|
import { convertNumbersForSaving, convertSettingToNumber } from '@nitrots/nitro-renderer';
|
||||||
import { FloorplanCanvasView } from './views/FloorplanCanvasView';
|
import { FloorplanCanvasView } from './views/FloorplanCanvasView';
|
||||||
import { FloorplanImportExportView } from './views/FloorplanImportExportView';
|
import { FloorplanImportExportView } from './views/FloorplanImportExportView';
|
||||||
import { FloorplanOptionsView } from './views/FloorplanOptionsView';
|
import { FloorplanOptionsView } from './views/FloorplanOptionsView';
|
||||||
import { FloorplanHeightSelector } from './views/FloorplanHeightSelector';
|
|
||||||
import { FloorplanPreviewView } from './views/FloorplanPreviewView';
|
|
||||||
|
|
||||||
const MIN_WALL_HEIGHT = 0;
|
|
||||||
const MAX_WALL_HEIGHT = 16;
|
type ScrollDirection = 'up' | 'down' | 'left' | 'right';
|
||||||
|
|
||||||
export const FloorplanEditorView: FC<{}> = props =>
|
export const FloorplanEditorView: FC<{}> = props =>
|
||||||
{
|
{
|
||||||
@@ -37,65 +34,7 @@ export const FloorplanEditorView: FC<{}> = props =>
|
|||||||
thicknessWall: 1,
|
thicknessWall: 1,
|
||||||
thicknessFloor: 1
|
thicknessFloor: 1
|
||||||
});
|
});
|
||||||
const [ floorHeight, setFloorHeight ] = useState(0);
|
const [ canvasScrollHandler, setCanvasScrollHandler ] = useState<((direction: ScrollDirection) => void) | null>(null);
|
||||||
const [ floorAction, setFloorAction ] = useState(FloorAction.SET);
|
|
||||||
const [ tilemapVersion, setTilemapVersion ] = useState(0);
|
|
||||||
const [ areaInfo, setAreaInfo ] = useState({ total: 0, walkable: 0 });
|
|
||||||
|
|
||||||
const calculateArea = useCallback(() =>
|
|
||||||
{
|
|
||||||
const tilemap = FloorplanEditor.instance.tilemap;
|
|
||||||
|
|
||||||
if(!tilemap || tilemap.length === 0)
|
|
||||||
{
|
|
||||||
setAreaInfo({ total: 0, walkable: 0 });
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let total = 0;
|
|
||||||
let walkable = 0;
|
|
||||||
|
|
||||||
for(let y = 0; y < tilemap.length; y++)
|
|
||||||
{
|
|
||||||
if(!tilemap[y]) continue;
|
|
||||||
|
|
||||||
for(let x = 0; x < tilemap[y].length; x++)
|
|
||||||
{
|
|
||||||
if(!tilemap[y][x] || tilemap[y][x].height === 'x') continue;
|
|
||||||
|
|
||||||
total++;
|
|
||||||
|
|
||||||
if(!tilemap[y][x].isBlocked) walkable++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setAreaInfo({ total, walkable });
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// sync floorHeight/floorAction changes to the FloorplanEditor instance
|
|
||||||
useEffect(() =>
|
|
||||||
{
|
|
||||||
FloorplanEditor.instance.actionSettings.currentAction = floorAction;
|
|
||||||
FloorplanEditor.instance.actionSettings.currentHeight = floorHeight.toString(36);
|
|
||||||
}, [ floorHeight, floorAction ]);
|
|
||||||
|
|
||||||
// register onTilemapChange callback
|
|
||||||
useEffect(() =>
|
|
||||||
{
|
|
||||||
if(!isVisible) return;
|
|
||||||
|
|
||||||
FloorplanEditor.instance.onTilemapChange = () =>
|
|
||||||
{
|
|
||||||
setTilemapVersion(prev => prev + 1);
|
|
||||||
calculateArea();
|
|
||||||
};
|
|
||||||
|
|
||||||
return () =>
|
|
||||||
{
|
|
||||||
FloorplanEditor.instance.onTilemapChange = null;
|
|
||||||
};
|
|
||||||
}, [ isVisible, calculateArea ]);
|
|
||||||
|
|
||||||
const saveFloorChanges = () =>
|
const saveFloorChanges = () =>
|
||||||
{
|
{
|
||||||
@@ -108,50 +47,16 @@ export const FloorplanEditorView: FC<{}> = props =>
|
|||||||
convertNumbersForSaving(visualizationSettings.thicknessFloor),
|
convertNumbersForSaving(visualizationSettings.thicknessFloor),
|
||||||
(visualizationSettings.wallHeight - 1)
|
(visualizationSettings.wallHeight - 1)
|
||||||
));
|
));
|
||||||
};
|
}
|
||||||
|
|
||||||
const revertChanges = () =>
|
const revertChanges = () =>
|
||||||
{
|
{
|
||||||
setVisualizationSettings({ wallHeight: originalFloorplanSettings.wallHeight, thicknessWall: originalFloorplanSettings.thicknessWall, thicknessFloor: originalFloorplanSettings.thicknessFloor, entryPointDir: originalFloorplanSettings.entryPointDir });
|
setVisualizationSettings({ wallHeight: originalFloorplanSettings.wallHeight, thicknessWall: originalFloorplanSettings.thicknessWall, thicknessFloor: originalFloorplanSettings.thicknessFloor, entryPointDir: originalFloorplanSettings.entryPointDir });
|
||||||
|
|
||||||
FloorplanEditor.instance.doorLocation = { x: originalFloorplanSettings.entryPoint[0], y: originalFloorplanSettings.entryPoint[1] };
|
FloorplanEditor.instance.doorLocation = { x: originalFloorplanSettings.entryPoint[0], y: originalFloorplanSettings.entryPoint[1] };
|
||||||
FloorplanEditor.instance.setTilemap(originalFloorplanSettings.tilemap, originalFloorplanSettings.reservedTiles);
|
FloorplanEditor.instance.setTilemap(originalFloorplanSettings.tilemap, originalFloorplanSettings.reservedTiles);
|
||||||
FloorplanEditor.instance.renderTiles();
|
FloorplanEditor.instance.renderTiles();
|
||||||
};
|
}
|
||||||
|
|
||||||
const onWallHeightChange = (value: number) =>
|
|
||||||
{
|
|
||||||
if(isNaN(value) || (value <= 0)) value = MIN_WALL_HEIGHT;
|
|
||||||
|
|
||||||
if(value > MAX_WALL_HEIGHT) value = MAX_WALL_HEIGHT;
|
|
||||||
|
|
||||||
setVisualizationSettings(prevValue =>
|
|
||||||
{
|
|
||||||
const newValue = { ...prevValue };
|
|
||||||
|
|
||||||
newValue.wallHeight = value;
|
|
||||||
|
|
||||||
return newValue;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const increaseWallHeight = () =>
|
|
||||||
{
|
|
||||||
let height = (visualizationSettings.wallHeight + 1);
|
|
||||||
|
|
||||||
if(height > MAX_WALL_HEIGHT) height = MAX_WALL_HEIGHT;
|
|
||||||
|
|
||||||
onWallHeightChange(height);
|
|
||||||
};
|
|
||||||
|
|
||||||
const decreaseWallHeight = () =>
|
|
||||||
{
|
|
||||||
let height = (visualizationSettings.wallHeight - 1);
|
|
||||||
|
|
||||||
if(height <= 0) height = MIN_WALL_HEIGHT;
|
|
||||||
|
|
||||||
onWallHeightChange(height);
|
|
||||||
};
|
|
||||||
|
|
||||||
useNitroEvent<RoomEngineEvent>(RoomEngineEvent.DISPOSED, event => setIsVisible(false));
|
useNitroEvent<RoomEngineEvent>(RoomEngineEvent.DISPOSED, event => setIsVisible(false));
|
||||||
|
|
||||||
@@ -212,7 +117,7 @@ export const FloorplanEditorView: FC<{}> = props =>
|
|||||||
const parts = url.split('/');
|
const parts = url.split('/');
|
||||||
|
|
||||||
if(parts.length < 2) return;
|
if(parts.length < 2) return;
|
||||||
|
|
||||||
switch(parts[1])
|
switch(parts[1])
|
||||||
{
|
{
|
||||||
case 'show':
|
case 'show':
|
||||||
@@ -235,42 +140,17 @@ export const FloorplanEditorView: FC<{}> = props =>
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FloorplanEditorContextProvider value={ {
|
<FloorplanEditorContextProvider value={ { originalFloorplanSettings: originalFloorplanSettings, setOriginalFloorplanSettings: setOriginalFloorplanSettings, visualizationSettings: visualizationSettings, setVisualizationSettings: setVisualizationSettings } }>
|
||||||
originalFloorplanSettings,
|
|
||||||
setOriginalFloorplanSettings,
|
|
||||||
visualizationSettings,
|
|
||||||
setVisualizationSettings,
|
|
||||||
floorHeight,
|
|
||||||
setFloorHeight,
|
|
||||||
floorAction,
|
|
||||||
setFloorAction,
|
|
||||||
tilemapVersion,
|
|
||||||
areaInfo
|
|
||||||
} }>
|
|
||||||
{ isVisible &&
|
{ isVisible &&
|
||||||
<NitroCardView uniqueKey="floorpan-editor" className="w-[1100px] h-[600px]" theme="primary-slim">
|
<NitroCardView uniqueKey="floorpan-editor" className="w-[760px] h-[500px]" theme="primary-slim">
|
||||||
<NitroCardHeaderView headerText={ LocalizeText('floor.plan.editor.title') } onCloseClick={ () => setIsVisible(false) } />
|
<NitroCardHeaderView headerText={ LocalizeText('floor.plan.editor.title') } onCloseClick={ () => setIsVisible(false) } />
|
||||||
<NitroCardContentView overflow="hidden" className="flex flex-col">
|
<NitroCardContentView overflow="hidden">
|
||||||
<FloorplanOptionsView />
|
<FloorplanOptionsView onCanvasScroll={ direction => canvasScrollHandler && canvasScrollHandler(direction) } />
|
||||||
<Flex gap={ 2 } className="flex-1 min-h-0">
|
<FloorplanCanvasView overflow="hidden" setScrollHandler={ setCanvasScrollHandler } />
|
||||||
<FloorplanHeightSelector />
|
|
||||||
<FloorplanCanvasView overflow="hidden" />
|
|
||||||
<Column gap={ 2 } className="w-[380px] min-w-[380px]">
|
|
||||||
<FloorplanPreviewView />
|
|
||||||
<Flex gap={ 1 } alignItems="center">
|
|
||||||
<Text bold small>{ LocalizeText('floor.editor.wall.height') }</Text>
|
|
||||||
<FaCaretLeft className="cursor-pointer fa-icon" onClick={ decreaseWallHeight } />
|
|
||||||
<input type="number" className="form-control form-control-sm w-[49px]" value={ visualizationSettings.wallHeight } onChange={ event => onWallHeightChange(event.target.valueAsNumber) } />
|
|
||||||
<FaCaretRight className="cursor-pointer fa-icon" onClick={ increaseWallHeight } />
|
|
||||||
</Flex>
|
|
||||||
<Text bold small className="text-center">
|
|
||||||
Area: { areaInfo.total } ({ areaInfo.walkable } caselle)
|
|
||||||
</Text>
|
|
||||||
</Column>
|
|
||||||
</Flex>
|
|
||||||
<Flex justifyContent="between">
|
<Flex justifyContent="between">
|
||||||
<Button variant="danger" onClick={ revertChanges }>{ LocalizeText('floor.plan.editor.reload') }</Button>
|
<Button onClick={ revertChanges }>{ LocalizeText('floor.plan.editor.reload') }</Button>
|
||||||
<ButtonGroup>
|
<ButtonGroup>
|
||||||
|
<Button disabled={ true }>{ LocalizeText('floor.plan.editor.preview') }</Button>
|
||||||
<Button onClick={ event => setImportExportVisible(true) }>{ LocalizeText('floor.plan.editor.import.export') }</Button>
|
<Button onClick={ event => setImportExportVisible(true) }>{ LocalizeText('floor.plan.editor.import.export') }</Button>
|
||||||
<Button onClick={ saveFloorChanges }>{ LocalizeText('floor.plan.editor.save') }</Button>
|
<Button onClick={ saveFloorChanges }>{ LocalizeText('floor.plan.editor.save') }</Button>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
@@ -281,4 +161,4 @@ export const FloorplanEditorView: FC<{}> = props =>
|
|||||||
<FloorplanImportExportView onCloseClick={ () => setImportExportVisible(false) } /> }
|
<FloorplanImportExportView onCloseClick={ () => setImportExportVisible(false) } /> }
|
||||||
</FloorplanEditorContextProvider>
|
</FloorplanEditorContextProvider>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|||||||
@@ -1,25 +1,25 @@
|
|||||||
import { GetOccupiedTilesMessageComposer, GetRoomEntryTileMessageComposer, RoomEntryTileMessageEvent, RoomOccupiedTilesMessageEvent } from '@nitrots/nitro-renderer';
|
import { GetOccupiedTilesMessageComposer, GetRoomEntryTileMessageComposer, RoomEntryTileMessageEvent, RoomOccupiedTilesMessageEvent } from '@nitrots/nitro-renderer';
|
||||||
import { FC, useEffect, useRef, useState } from 'react';
|
import { FC, useEffect, useRef, useState } from 'react';
|
||||||
import { FaPlus, FaMinus } from 'react-icons/fa';
|
|
||||||
import { SendMessageComposer } from '../../../api';
|
import { SendMessageComposer } from '../../../api';
|
||||||
import { Base, Column, ColumnProps } from '../../../common';
|
import { Base, Column, ColumnProps } from '../../../common';
|
||||||
import { useMessageEvent } from '../../../hooks';
|
import { useMessageEvent } from '../../../hooks';
|
||||||
import { useFloorplanEditorContext } from '../FloorplanEditorContext';
|
import { useFloorplanEditorContext } from '../FloorplanEditorContext';
|
||||||
import { FloorplanEditor } from '@nitrots/nitro-renderer';
|
import { FloorplanEditor } from '@nitrots/nitro-renderer';
|
||||||
|
|
||||||
|
type ScrollDirection = 'up' | 'down' | 'left' | 'right';
|
||||||
|
|
||||||
interface FloorplanCanvasViewProps extends ColumnProps
|
interface FloorplanCanvasViewProps extends ColumnProps
|
||||||
{
|
{
|
||||||
|
setScrollHandler(handler: ((direction: ScrollDirection) => void) | null): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FloorplanCanvasView: FC<FloorplanCanvasViewProps> = props =>
|
export const FloorplanCanvasView: FC<FloorplanCanvasViewProps> = props =>
|
||||||
{
|
{
|
||||||
const { gap = 1, children = null, ...rest } = props;
|
const { gap = 1, children = null, setScrollHandler = null, ...rest } = props;
|
||||||
const [ occupiedTilesReceived, setOccupiedTilesReceived ] = useState(false);
|
const [ occupiedTilesReceived , setOccupiedTilesReceived ] = useState(false);
|
||||||
const [ entryTileReceived, setEntryTileReceived ] = useState(false);
|
const [ entryTileReceived, setEntryTileReceived ] = useState(false);
|
||||||
const [ zoomLevel, setZoomLevel ] = useState(1.0);
|
|
||||||
const { originalFloorplanSettings = null, setOriginalFloorplanSettings = null, setVisualizationSettings = null } = useFloorplanEditorContext();
|
const { originalFloorplanSettings = null, setOriginalFloorplanSettings = null, setVisualizationSettings = null } = useFloorplanEditorContext();
|
||||||
const elementRef = useRef<HTMLDivElement>(null);
|
const elementRef = useRef<HTMLDivElement>(null);
|
||||||
const canvasWrapperRef = useRef<HTMLDivElement>(null);
|
|
||||||
|
|
||||||
useMessageEvent<RoomOccupiedTilesMessageEvent>(RoomOccupiedTilesMessageEvent, event =>
|
useMessageEvent<RoomOccupiedTilesMessageEvent>(RoomOccupiedTilesMessageEvent, event =>
|
||||||
{
|
{
|
||||||
@@ -37,7 +37,7 @@ export const FloorplanCanvasView: FC<FloorplanCanvasViewProps> = props =>
|
|||||||
});
|
});
|
||||||
|
|
||||||
setOccupiedTilesReceived(true);
|
setOccupiedTilesReceived(true);
|
||||||
|
|
||||||
elementRef.current.scrollTo((FloorplanEditor.instance.renderer.canvas.width / 3), 0);
|
elementRef.current.scrollTo((FloorplanEditor.instance.renderer.canvas.width / 3), 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -63,16 +63,39 @@ export const FloorplanCanvasView: FC<FloorplanCanvasViewProps> = props =>
|
|||||||
|
|
||||||
return newValue;
|
return newValue;
|
||||||
});
|
});
|
||||||
|
|
||||||
FloorplanEditor.instance.doorLocation = { x: parser.x, y: parser.y };
|
FloorplanEditor.instance.doorLocation = { x: parser.x, y: parser.y };
|
||||||
|
|
||||||
setEntryTileReceived(true);
|
setEntryTileReceived(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const onClickArrowButton = (scrollDirection: ScrollDirection) =>
|
||||||
|
{
|
||||||
|
const element = elementRef.current;
|
||||||
|
|
||||||
|
if(!element) return;
|
||||||
|
|
||||||
|
switch(scrollDirection)
|
||||||
|
{
|
||||||
|
case 'up':
|
||||||
|
element.scrollBy({ top: -10 });
|
||||||
|
break;
|
||||||
|
case 'down':
|
||||||
|
element.scrollBy({ top: 10 });
|
||||||
|
break;
|
||||||
|
case 'left':
|
||||||
|
element.scrollBy({ left: -10 });
|
||||||
|
break;
|
||||||
|
case 'right':
|
||||||
|
element.scrollBy({ left: 10 });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const onPointerEvent = (event: PointerEvent) =>
|
const onPointerEvent = (event: PointerEvent) =>
|
||||||
{
|
{
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
switch(event.type)
|
switch(event.type)
|
||||||
{
|
{
|
||||||
case 'pointerout':
|
case 'pointerout':
|
||||||
@@ -86,10 +109,7 @@ export const FloorplanCanvasView: FC<FloorplanCanvasViewProps> = props =>
|
|||||||
FloorplanEditor.instance.onPointerMove(event);
|
FloorplanEditor.instance.onPointerMove(event);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
const zoomIn = () => setZoomLevel(prev => Math.min(prev + 0.25, 2.0));
|
|
||||||
const zoomOut = () => setZoomLevel(prev => Math.max(prev - 0.25, 0.5));
|
|
||||||
|
|
||||||
useEffect(() =>
|
useEffect(() =>
|
||||||
{
|
{
|
||||||
@@ -104,15 +124,15 @@ export const FloorplanCanvasView: FC<FloorplanCanvasViewProps> = props =>
|
|||||||
thicknessWall: originalFloorplanSettings.thicknessWall,
|
thicknessWall: originalFloorplanSettings.thicknessWall,
|
||||||
thicknessFloor: originalFloorplanSettings.thicknessFloor,
|
thicknessFloor: originalFloorplanSettings.thicknessFloor,
|
||||||
entryPointDir: prevValue.entryPointDir
|
entryPointDir: prevValue.entryPointDir
|
||||||
};
|
}
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
}, [ originalFloorplanSettings.thicknessFloor, originalFloorplanSettings.thicknessWall, originalFloorplanSettings.wallHeight, setVisualizationSettings ]);
|
}, [ originalFloorplanSettings.thicknessFloor, originalFloorplanSettings.thicknessWall, originalFloorplanSettings.wallHeight, setVisualizationSettings ]);
|
||||||
|
|
||||||
useEffect(() =>
|
useEffect(() =>
|
||||||
{
|
{
|
||||||
if(!entryTileReceived || !occupiedTilesReceived) return;
|
if(!entryTileReceived || !occupiedTilesReceived) return;
|
||||||
|
|
||||||
FloorplanEditor.instance.renderTiles();
|
FloorplanEditor.instance.renderTiles();
|
||||||
}, [ entryTileReceived, occupiedTilesReceived ]);
|
}, [ entryTileReceived, occupiedTilesReceived ]);
|
||||||
|
|
||||||
@@ -124,56 +144,45 @@ export const FloorplanCanvasView: FC<FloorplanCanvasViewProps> = props =>
|
|||||||
const currentElement = elementRef.current;
|
const currentElement = elementRef.current;
|
||||||
|
|
||||||
if(!currentElement) return;
|
if(!currentElement) return;
|
||||||
|
|
||||||
const wrapper = canvasWrapperRef.current;
|
currentElement.appendChild(FloorplanEditor.instance.renderer.canvas);
|
||||||
|
|
||||||
if(wrapper) wrapper.appendChild(FloorplanEditor.instance.renderer.canvas);
|
|
||||||
|
|
||||||
currentElement.addEventListener('pointerup', onPointerEvent);
|
currentElement.addEventListener('pointerup', onPointerEvent);
|
||||||
|
|
||||||
currentElement.addEventListener('pointerout', onPointerEvent);
|
currentElement.addEventListener('pointerout', onPointerEvent);
|
||||||
|
|
||||||
currentElement.addEventListener('pointerdown', onPointerEvent);
|
currentElement.addEventListener('pointerdown', onPointerEvent);
|
||||||
|
|
||||||
currentElement.addEventListener('pointermove', onPointerEvent);
|
currentElement.addEventListener('pointermove', onPointerEvent);
|
||||||
|
|
||||||
return () =>
|
return () =>
|
||||||
{
|
{
|
||||||
if(currentElement)
|
if(currentElement)
|
||||||
{
|
{
|
||||||
currentElement.removeEventListener('pointerup', onPointerEvent);
|
currentElement.removeEventListener('pointerup', onPointerEvent);
|
||||||
|
|
||||||
currentElement.removeEventListener('pointerout', onPointerEvent);
|
currentElement.removeEventListener('pointerout', onPointerEvent);
|
||||||
|
|
||||||
currentElement.removeEventListener('pointerdown', onPointerEvent);
|
currentElement.removeEventListener('pointerdown', onPointerEvent);
|
||||||
|
|
||||||
currentElement.removeEventListener('pointermove', onPointerEvent);
|
currentElement.removeEventListener('pointermove', onPointerEvent);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() =>
|
||||||
|
{
|
||||||
|
if(!setScrollHandler) return;
|
||||||
|
|
||||||
|
setScrollHandler(() => onClickArrowButton);
|
||||||
|
|
||||||
|
return () => setScrollHandler(null);
|
||||||
|
}, [ setScrollHandler ]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column gap={ gap } { ...rest } className="relative flex-1">
|
<Column gap={ gap } { ...rest }>
|
||||||
<Base overflow="auto" innerRef={ elementRef } className="flex-1">
|
<Base overflow="auto" innerRef={ elementRef } />
|
||||||
<div
|
|
||||||
ref={ canvasWrapperRef }
|
|
||||||
style={ {
|
|
||||||
transform: `scale(${ zoomLevel })`,
|
|
||||||
transformOrigin: '0 0'
|
|
||||||
} }
|
|
||||||
/>
|
|
||||||
</Base>
|
|
||||||
<div className="absolute top-2 right-2 flex flex-col gap-1 z-10">
|
|
||||||
<button
|
|
||||||
className="w-[28px] h-[28px] flex items-center justify-center rounded bg-[#1e7295] text-white border border-transparent shadow cursor-pointer hover:brightness-110"
|
|
||||||
onClick={ zoomIn }
|
|
||||||
title="Zoom in"
|
|
||||||
>
|
|
||||||
<FaPlus size={ 10 } />
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className="w-[28px] h-[28px] flex items-center justify-center rounded bg-[#1e7295] text-white border border-transparent shadow cursor-pointer hover:brightness-110"
|
|
||||||
onClick={ zoomOut }
|
|
||||||
title="Zoom out"
|
|
||||||
>
|
|
||||||
<FaMinus size={ 10 } />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
{ children }
|
{ children }
|
||||||
</Column>
|
</Column>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|||||||
@@ -1,54 +0,0 @@
|
|||||||
import { FC } from 'react';
|
|
||||||
import { COLORMAP, FloorAction, HEIGHT_SCHEME } from '@nitrots/nitro-renderer';
|
|
||||||
import { FloorplanEditor } from '@nitrots/nitro-renderer';
|
|
||||||
import { Column, Text } from '../../../common';
|
|
||||||
import { useFloorplanEditorContext } from '../FloorplanEditorContext';
|
|
||||||
|
|
||||||
const colormap = COLORMAP as Record<string, string>;
|
|
||||||
|
|
||||||
export const FloorplanHeightSelector: FC<{}> = () =>
|
|
||||||
{
|
|
||||||
const { floorHeight, setFloorHeight, setFloorAction } = useFloorplanEditorContext();
|
|
||||||
|
|
||||||
const onSelectHeight = (height: number) =>
|
|
||||||
{
|
|
||||||
setFloorHeight(height);
|
|
||||||
setFloorAction(FloorAction.SET);
|
|
||||||
|
|
||||||
FloorplanEditor.instance.actionSettings.currentAction = FloorAction.SET;
|
|
||||||
FloorplanEditor.instance.actionSettings.currentHeight = height.toString(36);
|
|
||||||
};
|
|
||||||
|
|
||||||
const heights: number[] = [];
|
|
||||||
|
|
||||||
for(let i = 26; i >= 0; i--) heights.push(i);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Column className="h-full w-[30px] min-w-[30px] select-none">
|
|
||||||
<Text bold small center>{ floorHeight }</Text>
|
|
||||||
<div className="flex flex-col flex-1 rounded overflow-hidden border-2 border-muted">
|
|
||||||
{ heights.map(h =>
|
|
||||||
{
|
|
||||||
const char = HEIGHT_SCHEME[h + 1];
|
|
||||||
const color = colormap[char] || '101010';
|
|
||||||
const isActive = (floorHeight === h);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={ h }
|
|
||||||
className="flex-1 cursor-pointer relative flex items-center justify-center"
|
|
||||||
style={ {
|
|
||||||
backgroundColor: `#${ color }`,
|
|
||||||
outline: isActive ? '2px solid #fff' : 'none',
|
|
||||||
outlineOffset: '-2px',
|
|
||||||
zIndex: isActive ? 1 : 0
|
|
||||||
} }
|
|
||||||
onClick={ () => onSelectHeight(h) }
|
|
||||||
title={ `${ h }` }
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}) }
|
|
||||||
</div>
|
|
||||||
</Column>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,32 +1,45 @@
|
|||||||
import { FC } from 'react';
|
import { FC, useState } from 'react';
|
||||||
|
import { FaArrowDown, FaArrowLeft, FaArrowRight, FaArrowUp, FaCaretLeft, FaCaretRight } from 'react-icons/fa';
|
||||||
import { LocalizeText } from '../../../api';
|
import { LocalizeText } from '../../../api';
|
||||||
import { Flex, LayoutGridItem, Text } from '../../../common';
|
import { Button, Column, Flex, LayoutGridItem, Slider, Text } from '../../../common';
|
||||||
import { FloorAction } from '@nitrots/nitro-renderer';
|
import { COLORMAP, FloorAction } from '@nitrots/nitro-renderer';
|
||||||
import { FloorplanEditor } from '@nitrots/nitro-renderer';
|
import { FloorplanEditor } from '@nitrots/nitro-renderer';
|
||||||
import { useFloorplanEditorContext } from '../FloorplanEditorContext';
|
import { useFloorplanEditorContext } from '../FloorplanEditorContext';
|
||||||
|
|
||||||
|
const MIN_WALL_HEIGHT: number = 0;
|
||||||
|
const MAX_WALL_HEIGHT: number = 16;
|
||||||
|
|
||||||
|
const MIN_FLOOR_HEIGHT: number = 0;
|
||||||
|
const MAX_FLOOR_HEIGHT: number = 26;
|
||||||
|
|
||||||
|
type ScrollDirection = 'up' | 'down' | 'left' | 'right';
|
||||||
|
|
||||||
interface FloorplanOptionsViewProps
|
interface FloorplanOptionsViewProps
|
||||||
{
|
{
|
||||||
|
onCanvasScroll?(direction: ScrollDirection): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FloorplanOptionsView: FC<FloorplanOptionsViewProps> = props =>
|
export const FloorplanOptionsView: FC<FloorplanOptionsViewProps> = props =>
|
||||||
{
|
{
|
||||||
const { visualizationSettings = null, setVisualizationSettings = null, floorAction, setFloorAction } = useFloorplanEditorContext();
|
const { onCanvasScroll = () => {} } = props;
|
||||||
const isSquareSelectMode = FloorplanEditor.instance.isSquareSelectMode;
|
const { visualizationSettings = null, setVisualizationSettings = null } = useFloorplanEditorContext();
|
||||||
|
const [ floorAction, setFloorAction ] = useState(FloorAction.SET);
|
||||||
|
const [ floorHeight, setFloorHeight ] = useState(0);
|
||||||
|
const [ isSquareSelectMode, setSquareSelectMode ] = useState(false);
|
||||||
|
|
||||||
const selectAction = (action: number) =>
|
const selectAction = (action: number) =>
|
||||||
{
|
{
|
||||||
setFloorAction(action);
|
setFloorAction(action);
|
||||||
|
|
||||||
FloorplanEditor.instance.actionSettings.currentAction = action;
|
FloorplanEditor.instance.actionSettings.currentAction = action;
|
||||||
};
|
}
|
||||||
|
|
||||||
const toggleSquareSelectMode = () =>
|
const toggleSquareSelectMode = () =>
|
||||||
{
|
{
|
||||||
FloorplanEditor.instance.toggleSquareSelectMode();
|
const nextValue = FloorplanEditor.instance.toggleSquareSelectMode();
|
||||||
// force re-render by toggling action to same value
|
|
||||||
setFloorAction(prev => prev);
|
setSquareSelectMode(nextValue);
|
||||||
};
|
}
|
||||||
|
|
||||||
const changeDoorDirection = () =>
|
const changeDoorDirection = () =>
|
||||||
{
|
{
|
||||||
@@ -45,19 +58,18 @@ export const FloorplanOptionsView: FC<FloorplanOptionsViewProps> = props =>
|
|||||||
|
|
||||||
return newValue;
|
return newValue;
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
const onWallThicknessChange = (value: number) =>
|
const onFloorHeightChange = (value: number) =>
|
||||||
{
|
{
|
||||||
setVisualizationSettings(prevValue =>
|
if(isNaN(value) || (value <= 0)) value = 0;
|
||||||
{
|
|
||||||
const newValue = { ...prevValue };
|
|
||||||
|
|
||||||
newValue.thicknessWall = value;
|
if(value > 26) value = 26;
|
||||||
|
|
||||||
return newValue;
|
setFloorHeight(value);
|
||||||
});
|
|
||||||
};
|
FloorplanEditor.instance.actionSettings.currentHeight = value.toString(36);
|
||||||
|
}
|
||||||
|
|
||||||
const onFloorThicknessChange = (value: number) =>
|
const onFloorThicknessChange = (value: number) =>
|
||||||
{
|
{
|
||||||
@@ -69,54 +81,157 @@ export const FloorplanOptionsView: FC<FloorplanOptionsViewProps> = props =>
|
|||||||
|
|
||||||
return newValue;
|
return newValue;
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
|
const onWallThicknessChange = (value: number) =>
|
||||||
|
{
|
||||||
|
setVisualizationSettings(prevValue =>
|
||||||
|
{
|
||||||
|
const newValue = { ...prevValue };
|
||||||
|
|
||||||
|
newValue.thicknessWall = value;
|
||||||
|
|
||||||
|
return newValue;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const onWallHeightChange = (value: number) =>
|
||||||
|
{
|
||||||
|
if(isNaN(value) || (value <= 0)) value = MIN_WALL_HEIGHT;
|
||||||
|
|
||||||
|
if(value > MAX_WALL_HEIGHT) value = MAX_WALL_HEIGHT;
|
||||||
|
|
||||||
|
setVisualizationSettings(prevValue =>
|
||||||
|
{
|
||||||
|
const newValue = { ...prevValue };
|
||||||
|
|
||||||
|
newValue.wallHeight = value;
|
||||||
|
|
||||||
|
return newValue;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const increaseWallHeight = () =>
|
||||||
|
{
|
||||||
|
let height = (visualizationSettings.wallHeight + 1);
|
||||||
|
|
||||||
|
if(height > MAX_WALL_HEIGHT) height = MAX_WALL_HEIGHT;
|
||||||
|
|
||||||
|
onWallHeightChange(height);
|
||||||
|
}
|
||||||
|
|
||||||
|
const decreaseWallHeight = () =>
|
||||||
|
{
|
||||||
|
let height = (visualizationSettings.wallHeight - 1);
|
||||||
|
|
||||||
|
if(height <= 0) height = MIN_WALL_HEIGHT;
|
||||||
|
|
||||||
|
onWallHeightChange(height);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex gap={ 2 } alignItems="center">
|
<Column>
|
||||||
<Flex gap={ 1 } alignItems="center">
|
<Flex gap={ 1 }>
|
||||||
<Text bold small>{ LocalizeText('floor.plan.editor.draw.mode') }</Text>
|
<Column size={ 5 } gap={ 1 }>
|
||||||
<Flex gap={ 1 }>
|
<Text bold>{ LocalizeText('floor.plan.editor.draw.mode') }</Text>
|
||||||
<LayoutGridItem itemActive={ (floorAction === FloorAction.SET) } onClick={ () => selectAction(FloorAction.SET) }>
|
<Flex gap={ 3 }>
|
||||||
<i className="nitro-icon icon-set-tile" />
|
<Flex gap={ 1 }>
|
||||||
</LayoutGridItem>
|
<LayoutGridItem itemActive={ (floorAction === FloorAction.SET) } onClick={ event => selectAction(FloorAction.SET) }>
|
||||||
<LayoutGridItem itemActive={ (floorAction === FloorAction.UNSET) } onClick={ () => selectAction(FloorAction.UNSET) }>
|
<i className="nitro-icon icon-set-tile" />
|
||||||
<i className="nitro-icon icon-unset-tile" />
|
</LayoutGridItem>
|
||||||
</LayoutGridItem>
|
<LayoutGridItem itemActive={ (floorAction === FloorAction.UNSET) } onClick={ event => selectAction(FloorAction.UNSET) }>
|
||||||
<LayoutGridItem itemActive={ (floorAction === FloorAction.UP) } onClick={ () => selectAction(FloorAction.UP) }>
|
<i className="nitro-icon icon-unset-tile" />
|
||||||
<i className="nitro-icon icon-increase-height" />
|
</LayoutGridItem>
|
||||||
</LayoutGridItem>
|
</Flex>
|
||||||
<LayoutGridItem itemActive={ (floorAction === FloorAction.DOWN) } onClick={ () => selectAction(FloorAction.DOWN) }>
|
<Flex gap={ 1 }>
|
||||||
<i className="nitro-icon icon-decrease-height" />
|
<LayoutGridItem itemActive={ (floorAction === FloorAction.UP) } onClick={ event => selectAction(FloorAction.UP) }>
|
||||||
</LayoutGridItem>
|
<i className="nitro-icon icon-increase-height" />
|
||||||
<LayoutGridItem itemActive={ (floorAction === FloorAction.DOOR) } onClick={ () => selectAction(FloorAction.DOOR) }>
|
</LayoutGridItem>
|
||||||
<i className="nitro-icon icon-set-door" />
|
<LayoutGridItem itemActive={ (floorAction === FloorAction.DOWN) } onClick={ event => selectAction(FloorAction.DOWN) }>
|
||||||
</LayoutGridItem>
|
<i className="nitro-icon icon-decrease-height" />
|
||||||
<LayoutGridItem onClick={ () => FloorplanEditor.instance.toggleSelectAll() }>
|
</LayoutGridItem>
|
||||||
<i className={ `nitro-icon ${ floorAction === FloorAction.UNSET ? 'icon-set-deselect' : 'icon-set-select' }` } />
|
</Flex>
|
||||||
</LayoutGridItem>
|
<LayoutGridItem itemActive={ (floorAction === FloorAction.DOOR) } onClick={ event => selectAction(FloorAction.DOOR) }>
|
||||||
<LayoutGridItem itemActive={ isSquareSelectMode } onClick={ toggleSquareSelectMode }>
|
<i className="nitro-icon icon-set-door" />
|
||||||
<i className={ `nitro-icon ${ isSquareSelectMode ? 'icon-set-active-squaresselect' : 'icon-set-squaresselect' }` } />
|
</LayoutGridItem>
|
||||||
</LayoutGridItem>
|
<LayoutGridItem onClick={ event => FloorplanEditor.instance.toggleSelectAll() }>
|
||||||
</Flex>
|
<i className={ `nitro-icon ${ floorAction === FloorAction.UNSET ? 'icon-set-deselect' : 'icon-set-select' }` } />
|
||||||
|
</LayoutGridItem>
|
||||||
|
<LayoutGridItem itemActive={ isSquareSelectMode } onClick={ toggleSquareSelectMode }>
|
||||||
|
<i className={ `nitro-icon ${ isSquareSelectMode ? 'icon-set-active-squaresselect' : 'icon-set-squaresselect' }` } />
|
||||||
|
</LayoutGridItem>
|
||||||
|
</Flex>
|
||||||
|
</Column>
|
||||||
|
<Column alignItems="center" size={ 4 }>
|
||||||
|
<Text bold>{ LocalizeText('floor.plan.editor.enter.direction') }</Text>
|
||||||
|
<i className={ `nitro-icon icon-door-direction-${ visualizationSettings.entryPointDir } cursor-pointer` } onClick={ changeDoorDirection } />
|
||||||
|
</Column>
|
||||||
|
<Column size={ 3 }>
|
||||||
|
<Text bold>{ LocalizeText('floor.editor.wall.height') }</Text>
|
||||||
|
<Flex alignItems="center" gap={ 1 }>
|
||||||
|
<FaCaretLeft className="cursor-pointer fa-icon" onClick={ decreaseWallHeight } />
|
||||||
|
<input type="number" className="form-control form-control-sm w-[49px]" value={ visualizationSettings.wallHeight } onChange={ event => onWallHeightChange(event.target.valueAsNumber) } />
|
||||||
|
<FaCaretRight className="cursor-pointer fa-icon" onClick={ increaseWallHeight } />
|
||||||
|
</Flex>
|
||||||
|
</Column>
|
||||||
|
<Column size={ 6 }>
|
||||||
|
<Text bold>{ LocalizeText('floor.plan.editor.room.options') }</Text>
|
||||||
|
<Flex className="align-items-center">
|
||||||
|
<select className="form-control form-control-sm" value={ visualizationSettings.thicknessWall } onChange={ event => onWallThicknessChange(parseInt(event.target.value)) }>
|
||||||
|
<option value={ 0 }>{ LocalizeText('navigator.roomsettings.wall_thickness.thinnest') }</option>
|
||||||
|
<option value={ 1 }>{ LocalizeText('navigator.roomsettings.wall_thickness.thin') }</option>
|
||||||
|
<option value={ 2 }>{ LocalizeText('navigator.roomsettings.wall_thickness.normal') }</option>
|
||||||
|
<option value={ 3 }>{ LocalizeText('navigator.roomsettings.wall_thickness.thick') }</option>
|
||||||
|
</select>
|
||||||
|
<select className="form-control form-control-sm" value={ visualizationSettings.thicknessFloor } onChange={ event => onFloorThicknessChange(parseInt(event.target.value)) }>
|
||||||
|
<option value={ 0 }>{ LocalizeText('navigator.roomsettings.floor_thickness.thinnest') }</option>
|
||||||
|
<option value={ 1 }>{ LocalizeText('navigator.roomsettings.floor_thickness.thin') }</option>
|
||||||
|
<option value={ 2 }>{ LocalizeText('navigator.roomsettings.floor_thickness.normal') }</option>
|
||||||
|
<option value={ 3 }>{ LocalizeText('navigator.roomsettings.floor_thickness.thick') }</option>
|
||||||
|
</select>
|
||||||
|
</Flex>
|
||||||
|
</Column>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex gap={ 1 } alignItems="center">
|
<Flex gap={ 2 } alignItems="center" justifyContent="between">
|
||||||
<Text bold small>{ LocalizeText('floor.plan.editor.enter.direction') }</Text>
|
<Column size={ 6 }>
|
||||||
<i className={ `nitro-icon icon-door-direction-${ visualizationSettings.entryPointDir } cursor-pointer` } onClick={ changeDoorDirection } />
|
<Text bold>{ LocalizeText('floor.plan.editor.tile.height') }: { floorHeight }</Text>
|
||||||
|
<div style={ { width: '100%', maxWidth: 240 } }>
|
||||||
|
<Slider
|
||||||
|
min={ MIN_FLOOR_HEIGHT }
|
||||||
|
max={ MAX_FLOOR_HEIGHT }
|
||||||
|
step={ 1 }
|
||||||
|
value={ floorHeight }
|
||||||
|
onChange={ event => onFloorHeightChange(event) }
|
||||||
|
renderThumb={ (props, state) =>
|
||||||
|
{
|
||||||
|
const { key, style, ...rest } = (props as Record<string, any>);
|
||||||
|
|
||||||
|
return <div key={ key } style={ { backgroundColor: `#${ COLORMAP[state.valueNow.toString(33)] }`, ...style } } { ...rest }>{ state.valueNow }</div>;
|
||||||
|
} } />
|
||||||
|
</div>
|
||||||
|
</Column>
|
||||||
|
<Column gap={ 1 }>
|
||||||
|
<Flex justifyContent="center">
|
||||||
|
<Button shrink onClick={ event => onCanvasScroll('up') }>
|
||||||
|
<FaArrowUp className="fa-icon" />
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
<Flex alignItems="center" justifyContent="center" gap={ 1 }>
|
||||||
|
<Button shrink onClick={ event => onCanvasScroll('left') }>
|
||||||
|
<FaArrowLeft className="fa-icon" />
|
||||||
|
</Button>
|
||||||
|
<div style={ { width: 28 } } />
|
||||||
|
<Button shrink onClick={ event => onCanvasScroll('right') }>
|
||||||
|
<FaArrowRight className="fa-icon" />
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
<Flex justifyContent="center">
|
||||||
|
<Button shrink onClick={ event => onCanvasScroll('down') }>
|
||||||
|
<FaArrowDown className="fa-icon" />
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</Column>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex gap={ 1 } alignItems="center" className="ml-auto">
|
</Column>
|
||||||
<select className="form-control form-control-sm" value={ visualizationSettings.thicknessWall } onChange={ event => onWallThicknessChange(parseInt(event.target.value)) }>
|
|
||||||
<option value={ 0 }>{ LocalizeText('navigator.roomsettings.wall_thickness.thinnest') }</option>
|
|
||||||
<option value={ 1 }>{ LocalizeText('navigator.roomsettings.wall_thickness.thin') }</option>
|
|
||||||
<option value={ 2 }>{ LocalizeText('navigator.roomsettings.wall_thickness.normal') }</option>
|
|
||||||
<option value={ 3 }>{ LocalizeText('navigator.roomsettings.wall_thickness.thick') }</option>
|
|
||||||
</select>
|
|
||||||
<select className="form-control form-control-sm" value={ visualizationSettings.thicknessFloor } onChange={ event => onFloorThicknessChange(parseInt(event.target.value)) }>
|
|
||||||
<option value={ 0 }>{ LocalizeText('navigator.roomsettings.floor_thickness.thinnest') }</option>
|
|
||||||
<option value={ 1 }>{ LocalizeText('navigator.roomsettings.floor_thickness.thin') }</option>
|
|
||||||
<option value={ 2 }>{ LocalizeText('navigator.roomsettings.floor_thickness.normal') }</option>
|
|
||||||
<option value={ 3 }>{ LocalizeText('navigator.roomsettings.floor_thickness.thick') }</option>
|
|
||||||
</select>
|
|
||||||
</Flex>
|
|
||||||
</Flex>
|
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
@@ -1,328 +0,0 @@
|
|||||||
import { FC, useEffect, useRef } from 'react';
|
|
||||||
import { COLORMAP, HEIGHT_SCHEME, FloorplanEditor } from '@nitrots/nitro-renderer';
|
|
||||||
import { useFloorplanEditorContext } from '../FloorplanEditorContext';
|
|
||||||
|
|
||||||
const colormap = COLORMAP as Record<string, string>;
|
|
||||||
|
|
||||||
const PREVIEW_TILE_W = 16;
|
|
||||||
const PREVIEW_TILE_H = 8;
|
|
||||||
const PREVIEW_BLOCK_H = 5;
|
|
||||||
const WALL_HEIGHT_PX = 40;
|
|
||||||
const WALL_COLOR = '#6B7B5E';
|
|
||||||
const WALL_SIDE_COLOR = '#5A6A4F';
|
|
||||||
const WALL_TOP_COLOR = '#7D8E6F';
|
|
||||||
|
|
||||||
function hexToRgb(hex: string): [number, number, number]
|
|
||||||
{
|
|
||||||
const r = parseInt(hex.substring(0, 2), 16);
|
|
||||||
const g = parseInt(hex.substring(2, 4), 16);
|
|
||||||
const b = parseInt(hex.substring(4, 6), 16);
|
|
||||||
|
|
||||||
return [ r, g, b ];
|
|
||||||
}
|
|
||||||
|
|
||||||
function rgbToHex(r: number, g: number, b: number): string
|
|
||||||
{
|
|
||||||
return `#${ ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1) }`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function darken(hex: string, factor: number): string
|
|
||||||
{
|
|
||||||
const [ r, g, b ] = hexToRgb(hex);
|
|
||||||
|
|
||||||
return rgbToHex(
|
|
||||||
Math.floor(r * factor),
|
|
||||||
Math.floor(g * factor),
|
|
||||||
Math.floor(b * factor)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTilemapBounds(tilemap: any[][]): { minX: number; minY: number; maxX: number; maxY: number }
|
|
||||||
{
|
|
||||||
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
||||||
|
|
||||||
for(let y = 0; y < tilemap.length; y++)
|
|
||||||
{
|
|
||||||
if(!tilemap[y]) continue;
|
|
||||||
|
|
||||||
for(let x = 0; x < tilemap[y].length; x++)
|
|
||||||
{
|
|
||||||
if(!tilemap[y][x] || tilemap[y][x].height === 'x') continue;
|
|
||||||
|
|
||||||
if(x < minX) minX = x;
|
|
||||||
if(x > maxX) maxX = x;
|
|
||||||
if(y < minY) minY = y;
|
|
||||||
if(y > maxY) maxY = y;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(minX === Infinity) return { minX: 0, minY: 0, maxX: 0, maxY: 0 };
|
|
||||||
|
|
||||||
return { minX, minY, maxX, maxY };
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderPreview(canvas: HTMLCanvasElement, wallHeight: number): void
|
|
||||||
{
|
|
||||||
const ctx = canvas.getContext('2d');
|
|
||||||
const tilemap = FloorplanEditor.instance.tilemap;
|
|
||||||
|
|
||||||
if(!ctx || !tilemap || tilemap.length === 0)
|
|
||||||
{
|
|
||||||
if(ctx)
|
|
||||||
{
|
|
||||||
ctx.fillStyle = '#1a1a1a';
|
|
||||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const bounds = getTilemapBounds(tilemap);
|
|
||||||
const tilesW = bounds.maxX - bounds.minX + 1;
|
|
||||||
const tilesH = bounds.maxY - bounds.minY + 1;
|
|
||||||
|
|
||||||
// find max height for offset calculation
|
|
||||||
let maxTileHeight = 0;
|
|
||||||
|
|
||||||
for(let y = bounds.minY; y <= bounds.maxY; y++)
|
|
||||||
{
|
|
||||||
for(let x = bounds.minX; x <= bounds.maxX; x++)
|
|
||||||
{
|
|
||||||
if(!tilemap[y] || !tilemap[y][x] || tilemap[y][x].height === 'x') continue;
|
|
||||||
|
|
||||||
const hi = HEIGHT_SCHEME.indexOf(tilemap[y][x].height) - 1;
|
|
||||||
|
|
||||||
if(hi > maxTileHeight) maxTileHeight = hi;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// calculate isometric bounds
|
|
||||||
const isoW = (tilesW + tilesH) * PREVIEW_TILE_W;
|
|
||||||
const isoH = (tilesW + tilesH) * PREVIEW_TILE_H + maxTileHeight * PREVIEW_BLOCK_H + WALL_HEIGHT_PX;
|
|
||||||
|
|
||||||
// scale to fit canvas
|
|
||||||
const scaleX = (canvas.width - 20) / isoW;
|
|
||||||
const scaleY = (canvas.height - 20) / isoH;
|
|
||||||
const scale = Math.min(scaleX, scaleY, 3);
|
|
||||||
|
|
||||||
const offsetX = (canvas.width - isoW * scale) / 2;
|
|
||||||
const offsetY = (canvas.height - isoH * scale) / 2 + WALL_HEIGHT_PX * scale * 0.5;
|
|
||||||
|
|
||||||
ctx.fillStyle = '#1a1a1a';
|
|
||||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
||||||
|
|
||||||
ctx.save();
|
|
||||||
ctx.translate(offsetX, offsetY);
|
|
||||||
ctx.scale(scale, scale);
|
|
||||||
|
|
||||||
const tw = PREVIEW_TILE_W;
|
|
||||||
const th = PREVIEW_TILE_H;
|
|
||||||
|
|
||||||
function isoX(gx: number, gy: number): number
|
|
||||||
{
|
|
||||||
return (gx - bounds.minX - gy + bounds.minY) * tw + (tilesH - 1) * tw;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isoY(gx: number, gy: number): number
|
|
||||||
{
|
|
||||||
return (gx - bounds.minX + gy - bounds.minY) * th;
|
|
||||||
}
|
|
||||||
|
|
||||||
function hasActiveTile(gx: number, gy: number): boolean
|
|
||||||
{
|
|
||||||
return tilemap[gy] && tilemap[gy][gx] && tilemap[gy][gx].height !== 'x';
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTileHeight(gx: number, gy: number): number
|
|
||||||
{
|
|
||||||
if(!hasActiveTile(gx, gy)) return 0;
|
|
||||||
|
|
||||||
return Math.max(0, HEIGHT_SCHEME.indexOf(tilemap[gy][gx].height) - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// draw walls on north and west edges
|
|
||||||
const wallH = wallHeight > 0 ? wallHeight * PREVIEW_BLOCK_H + WALL_HEIGHT_PX * 0.3 : WALL_HEIGHT_PX * 0.6;
|
|
||||||
|
|
||||||
for(let y = bounds.minY; y <= bounds.maxY; y++)
|
|
||||||
{
|
|
||||||
for(let x = bounds.minX; x <= bounds.maxX; x++)
|
|
||||||
{
|
|
||||||
if(!hasActiveTile(x, y)) continue;
|
|
||||||
|
|
||||||
const tileH = getTileHeight(x, y) * PREVIEW_BLOCK_H;
|
|
||||||
const cx = isoX(x, y);
|
|
||||||
const cy = isoY(x, y) - tileH;
|
|
||||||
|
|
||||||
// west wall (no tile to the left)
|
|
||||||
if(!hasActiveTile(x - 1, y))
|
|
||||||
{
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.moveTo(cx, cy + th);
|
|
||||||
ctx.lineTo(cx, cy + th - wallH);
|
|
||||||
ctx.lineTo(cx + tw, cy - wallH);
|
|
||||||
ctx.lineTo(cx + tw, cy);
|
|
||||||
ctx.closePath();
|
|
||||||
ctx.fillStyle = WALL_SIDE_COLOR;
|
|
||||||
ctx.fill();
|
|
||||||
ctx.strokeStyle = '#4A5A3F';
|
|
||||||
ctx.lineWidth = 0.5;
|
|
||||||
ctx.stroke();
|
|
||||||
}
|
|
||||||
|
|
||||||
// north wall (no tile above)
|
|
||||||
if(!hasActiveTile(x, y - 1))
|
|
||||||
{
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.moveTo(cx + tw, cy);
|
|
||||||
ctx.lineTo(cx + tw, cy - wallH);
|
|
||||||
ctx.lineTo(cx + tw * 2, cy + th - wallH);
|
|
||||||
ctx.lineTo(cx + tw * 2, cy + th);
|
|
||||||
ctx.closePath();
|
|
||||||
ctx.fillStyle = WALL_COLOR;
|
|
||||||
ctx.fill();
|
|
||||||
ctx.strokeStyle = '#4A5A3F';
|
|
||||||
ctx.lineWidth = 0.5;
|
|
||||||
ctx.stroke();
|
|
||||||
}
|
|
||||||
|
|
||||||
// wall top cap - corner
|
|
||||||
if(!hasActiveTile(x - 1, y) && !hasActiveTile(x, y - 1))
|
|
||||||
{
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.moveTo(cx + tw, cy - wallH);
|
|
||||||
ctx.lineTo(cx + tw + tw * 0.3, cy - wallH - th * 0.3);
|
|
||||||
ctx.lineTo(cx + tw, cy - wallH - th * 0.6);
|
|
||||||
ctx.lineTo(cx + tw - tw * 0.3, cy - wallH - th * 0.3);
|
|
||||||
ctx.closePath();
|
|
||||||
ctx.fillStyle = WALL_TOP_COLOR;
|
|
||||||
ctx.fill();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// draw tiles back-to-front
|
|
||||||
for(let y = bounds.minY; y <= bounds.maxY; y++)
|
|
||||||
{
|
|
||||||
for(let x = bounds.minX; x <= bounds.maxX; x++)
|
|
||||||
{
|
|
||||||
if(!hasActiveTile(x, y)) continue;
|
|
||||||
|
|
||||||
const tile = tilemap[y][x];
|
|
||||||
const heightIndex = HEIGHT_SCHEME.indexOf(tile.height) - 1;
|
|
||||||
const tileH = Math.max(0, heightIndex) * PREVIEW_BLOCK_H;
|
|
||||||
|
|
||||||
const cx = isoX(x, y);
|
|
||||||
const cy = isoY(x, y) - tileH;
|
|
||||||
|
|
||||||
const heightChar = tile.height;
|
|
||||||
const baseColor = colormap[heightChar] || 'aaaaaa';
|
|
||||||
const topColor = `#${ baseColor }`;
|
|
||||||
const leftColor = darken(baseColor, 0.65);
|
|
||||||
const rightColor = darken(baseColor, 0.80);
|
|
||||||
|
|
||||||
// draw side faces if tile has height
|
|
||||||
const blockH = Math.max(0, heightIndex) * PREVIEW_BLOCK_H;
|
|
||||||
|
|
||||||
// left face (visible when no neighbor to south or neighbor is shorter)
|
|
||||||
const southH = getTileHeight(x, y + 1);
|
|
||||||
const leftExpose = hasActiveTile(x, y + 1) ? Math.max(0, heightIndex - southH) * PREVIEW_BLOCK_H : blockH + PREVIEW_BLOCK_H;
|
|
||||||
|
|
||||||
if(leftExpose > 0)
|
|
||||||
{
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.moveTo(cx, cy + th);
|
|
||||||
ctx.lineTo(cx + tw, cy + th * 2);
|
|
||||||
ctx.lineTo(cx + tw, cy + th * 2 + leftExpose);
|
|
||||||
ctx.lineTo(cx, cy + th + leftExpose);
|
|
||||||
ctx.closePath();
|
|
||||||
ctx.fillStyle = leftColor;
|
|
||||||
ctx.fill();
|
|
||||||
}
|
|
||||||
|
|
||||||
// right face
|
|
||||||
const eastH = getTileHeight(x + 1, y);
|
|
||||||
const rightExpose = hasActiveTile(x + 1, y) ? Math.max(0, heightIndex - eastH) * PREVIEW_BLOCK_H : blockH + PREVIEW_BLOCK_H;
|
|
||||||
|
|
||||||
if(rightExpose > 0)
|
|
||||||
{
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.moveTo(cx + tw * 2, cy + th);
|
|
||||||
ctx.lineTo(cx + tw, cy + th * 2);
|
|
||||||
ctx.lineTo(cx + tw, cy + th * 2 + rightExpose);
|
|
||||||
ctx.lineTo(cx + tw * 2, cy + th + rightExpose);
|
|
||||||
ctx.closePath();
|
|
||||||
ctx.fillStyle = rightColor;
|
|
||||||
ctx.fill();
|
|
||||||
}
|
|
||||||
|
|
||||||
// top face
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.moveTo(cx + tw, cy);
|
|
||||||
ctx.lineTo(cx + tw * 2, cy + th);
|
|
||||||
ctx.lineTo(cx + tw, cy + th * 2);
|
|
||||||
ctx.lineTo(cx, cy + th);
|
|
||||||
ctx.closePath();
|
|
||||||
ctx.fillStyle = topColor;
|
|
||||||
ctx.fill();
|
|
||||||
|
|
||||||
// door indicator
|
|
||||||
const door = FloorplanEditor.instance.doorLocation;
|
|
||||||
|
|
||||||
if(door.x === x && door.y === y)
|
|
||||||
{
|
|
||||||
ctx.fillStyle = 'rgba(255, 255, 255, 0.5)';
|
|
||||||
ctx.fill();
|
|
||||||
ctx.strokeStyle = '#ffffff';
|
|
||||||
ctx.lineWidth = 1;
|
|
||||||
ctx.stroke();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.restore();
|
|
||||||
}
|
|
||||||
|
|
||||||
export const FloorplanPreviewView: FC<{}> = () =>
|
|
||||||
{
|
|
||||||
const { tilemapVersion, visualizationSettings } = useFloorplanEditorContext();
|
|
||||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
|
||||||
const rafRef = useRef<number>(0);
|
|
||||||
|
|
||||||
useEffect(() =>
|
|
||||||
{
|
|
||||||
if(!canvasRef.current) return;
|
|
||||||
|
|
||||||
if(rafRef.current) cancelAnimationFrame(rafRef.current);
|
|
||||||
|
|
||||||
rafRef.current = requestAnimationFrame(() =>
|
|
||||||
{
|
|
||||||
const canvas = canvasRef.current;
|
|
||||||
|
|
||||||
if(!canvas) return;
|
|
||||||
|
|
||||||
const parent = canvas.parentElement;
|
|
||||||
|
|
||||||
if(parent)
|
|
||||||
{
|
|
||||||
canvas.width = parent.clientWidth;
|
|
||||||
canvas.height = parent.clientHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderPreview(canvas, visualizationSettings?.wallHeight ?? 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
return () =>
|
|
||||||
{
|
|
||||||
if(rafRef.current) cancelAnimationFrame(rafRef.current);
|
|
||||||
};
|
|
||||||
}, [ tilemapVersion, visualizationSettings?.wallHeight ]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex-1 relative rounded overflow-hidden border-2 border-muted" style={ { minHeight: 200, backgroundColor: '#1a1a1a' } }>
|
|
||||||
<canvas
|
|
||||||
ref={ canvasRef }
|
|
||||||
className="w-full h-full"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
Reference in New Issue
Block a user