mirror of
https://github.com/duckietm/Nitro-Cool-UI.git
synced 2025-06-21 22:36:58 +00:00
🆙 First Stage !
This commit is contained in:
parent
ba63e68b92
commit
a59c093e04
@ -21,7 +21,7 @@ $toolbar-height: 55px;
|
|||||||
$achievement-width: 375px;
|
$achievement-width: 375px;
|
||||||
$achievement-height: 405px;
|
$achievement-height: 405px;
|
||||||
|
|
||||||
$avatar-editor-width: 520px;
|
$avatar-editor-width: 545px;
|
||||||
$avatar-editor-height: 553px;
|
$avatar-editor-height: 553px;
|
||||||
|
|
||||||
$backgrounds-width: 534px;
|
$backgrounds-width: 534px;
|
||||||
|
@ -1 +1 @@
|
|||||||
export const GetUIVersion = () => '2.1.1';
|
export const GetUIVersion = () => '2.2.5';
|
||||||
|
@ -3,6 +3,9 @@ import { CSSProperties, FC, useEffect, useMemo, useRef, useState } from 'react';
|
|||||||
import { GetAvatarRenderManager } from '../../api';
|
import { GetAvatarRenderManager } from '../../api';
|
||||||
import { Base, BaseProps } from '../Base';
|
import { Base, BaseProps } from '../Base';
|
||||||
|
|
||||||
|
// Cache for avatar image URLs
|
||||||
|
const AVATAR_IMAGE_CACHE: Map<string, { url: string }> = new Map();
|
||||||
|
|
||||||
export interface LayoutAvatarImageViewProps extends BaseProps<HTMLDivElement>
|
export interface LayoutAvatarImageViewProps extends BaseProps<HTMLDivElement>
|
||||||
{
|
{
|
||||||
figure: string;
|
figure: string;
|
||||||
@ -15,75 +18,166 @@ export interface LayoutAvatarImageViewProps extends BaseProps<HTMLDivElement>
|
|||||||
export const LayoutAvatarImageView: FC<LayoutAvatarImageViewProps> = props =>
|
export const LayoutAvatarImageView: FC<LayoutAvatarImageViewProps> = props =>
|
||||||
{
|
{
|
||||||
const { figure = '', gender = 'M', headOnly = false, direction = 0, scale = 1, classNames = [], style = {}, ...rest } = props;
|
const { figure = '', gender = 'M', headOnly = false, direction = 0, scale = 1, classNames = [], style = {}, ...rest } = props;
|
||||||
const [ avatarUrl, setAvatarUrl ] = useState<string>(null);
|
const [ avatarUrl, setAvatarUrl ] = useState<string | null>(null);
|
||||||
const [ randomValue, setRandomValue ] = useState(-1);
|
const [ isReady, setIsReady ] = useState<boolean>(false);
|
||||||
const isDisposed = useRef(false);
|
const isDisposed = useRef(false);
|
||||||
|
const retryCount = useRef(0);
|
||||||
|
const maxRetries = 3;
|
||||||
|
const elementRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const figureKey = useMemo(() => [ figure, gender, direction, headOnly ].join('-'), [ figure, gender, direction, headOnly ]);
|
||||||
|
|
||||||
const getClassNames = useMemo(() =>
|
const getClassNames = useMemo(() =>
|
||||||
{
|
{
|
||||||
const newClassNames: string[] = [ 'avatar-image' ];
|
const newClassNames: string[] = [ 'avatar-image' ];
|
||||||
|
|
||||||
|
if(headOnly) newClassNames.push('head-only');
|
||||||
|
|
||||||
if(classNames.length) newClassNames.push(...classNames);
|
if(classNames.length) newClassNames.push(...classNames);
|
||||||
|
|
||||||
return newClassNames;
|
return newClassNames;
|
||||||
}, [ classNames ]);
|
}, [ classNames, headOnly ]);
|
||||||
|
|
||||||
const getStyle = useMemo(() =>
|
const getStyle = useMemo(() =>
|
||||||
{
|
{
|
||||||
let newStyle: CSSProperties = {};
|
let newStyle: CSSProperties = {};
|
||||||
|
|
||||||
if(avatarUrl && avatarUrl.length) newStyle.backgroundImage = `url('${ avatarUrl }')`;
|
if(avatarUrl && avatarUrl.length)
|
||||||
|
{
|
||||||
|
newStyle.backgroundImage = `url('${ avatarUrl }')`;
|
||||||
|
}
|
||||||
|
|
||||||
|
newStyle.backgroundRepeat = 'no-repeat';
|
||||||
|
newStyle.backgroundPosition = headOnly ? 'center -8px' : 'center';
|
||||||
|
newStyle.position = 'relative';
|
||||||
|
|
||||||
if(scale !== 1)
|
if(scale !== 1)
|
||||||
{
|
{
|
||||||
newStyle.transform = `scale(${ scale })`;
|
newStyle.transform = `scale(${ scale })`;
|
||||||
|
|
||||||
if(!(scale % 1)) newStyle.imageRendering = 'pixelated';
|
if(!(scale % 1)) newStyle.imageRendering = 'pixelated';
|
||||||
}
|
}
|
||||||
|
|
||||||
if(Object.keys(style).length) newStyle = { ...newStyle, ...style };
|
if(Object.keys(style).length) newStyle = { ...newStyle, ...style };
|
||||||
|
|
||||||
return newStyle;
|
return newStyle;
|
||||||
}, [ avatarUrl, scale, style ]);
|
}, [ avatarUrl, scale, style, headOnly ]);
|
||||||
|
|
||||||
useEffect(() =>
|
const loadAvatarImage = async () =>
|
||||||
{
|
{
|
||||||
|
if(isDisposed.current) return;
|
||||||
|
|
||||||
|
if(!figure || figure.length === 0)
|
||||||
|
{
|
||||||
|
setAvatarUrl(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cached = AVATAR_IMAGE_CACHE.get(figureKey);
|
||||||
|
if(cached)
|
||||||
|
{
|
||||||
|
setAvatarUrl(cached.url);
|
||||||
|
retryCount.current = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const avatarImage = GetAvatarRenderManager().createAvatarImage(figure, AvatarScaleType.LARGE, gender, {
|
const avatarImage = GetAvatarRenderManager().createAvatarImage(figure, AvatarScaleType.LARGE, gender, {
|
||||||
resetFigure: figure =>
|
resetFigure: figure =>
|
||||||
{
|
{
|
||||||
if(isDisposed.current) return;
|
if(isDisposed.current) return;
|
||||||
|
loadAvatarImage();
|
||||||
setRandomValue(Math.random());
|
|
||||||
},
|
},
|
||||||
dispose: () =>
|
dispose: () => {},
|
||||||
{},
|
|
||||||
disposed: false
|
disposed: false
|
||||||
}, null);
|
}, null);
|
||||||
|
|
||||||
if(!avatarImage) return;
|
if(!avatarImage)
|
||||||
|
{
|
||||||
let setType = AvatarSetType.FULL;
|
if(retryCount.current < maxRetries)
|
||||||
|
{
|
||||||
if(headOnly) setType = AvatarSetType.HEAD;
|
retryCount.current += 1;
|
||||||
|
setTimeout(loadAvatarImage, 1000);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
setAvatarUrl(null);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const setType = headOnly ? AvatarSetType.HEAD : AvatarSetType.FULL;
|
||||||
avatarImage.setDirection(setType, direction);
|
avatarImage.setDirection(setType, direction);
|
||||||
|
|
||||||
const image = avatarImage.getCroppedImage(setType);
|
const image = await avatarImage.getCroppedImage(setType);
|
||||||
|
|
||||||
if(image) setAvatarUrl(image.src);
|
if(isDisposed.current) return;
|
||||||
|
|
||||||
avatarImage.dispose();
|
if(image && image.src && typeof image.src === 'string' && image.src.startsWith('data:image/'))
|
||||||
}, [ figure, gender, direction, headOnly, randomValue ]);
|
{
|
||||||
|
setAvatarUrl(image.src);
|
||||||
|
AVATAR_IMAGE_CACHE.set(figureKey, { url: image.src });
|
||||||
|
retryCount.current = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if(retryCount.current < maxRetries)
|
||||||
|
{
|
||||||
|
retryCount.current += 1;
|
||||||
|
setTimeout(loadAvatarImage, 1000);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
setAvatarUrl(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(error)
|
||||||
|
{
|
||||||
|
console.warn(`LayoutAvatarImageView: Error loading avatar image`, error);
|
||||||
|
if(retryCount.current < maxRetries)
|
||||||
|
{
|
||||||
|
retryCount.current += 1;
|
||||||
|
setTimeout(loadAvatarImage, 1000);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
setAvatarUrl(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
setTimeout(() => {
|
||||||
|
if(!isDisposed.current) avatarImage.dispose();
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() =>
|
useEffect(() =>
|
||||||
{
|
{
|
||||||
isDisposed.current = false;
|
isDisposed.current = false;
|
||||||
|
retryCount.current = 0;
|
||||||
|
setIsReady(true);
|
||||||
|
|
||||||
|
loadAvatarImage();
|
||||||
|
|
||||||
|
const handleVisibilityChange = () =>
|
||||||
|
{
|
||||||
|
if(document.visibilityState === 'visible')
|
||||||
|
{
|
||||||
|
setAvatarUrl(null);
|
||||||
|
loadAvatarImage();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener('visibilitychange', handleVisibilityChange);
|
||||||
|
|
||||||
return () =>
|
return () =>
|
||||||
{
|
{
|
||||||
isDisposed.current = true;
|
isDisposed.current = true;
|
||||||
}
|
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
||||||
}, []);
|
};
|
||||||
|
}, [ figure, gender, direction, headOnly, figureKey ]);
|
||||||
|
|
||||||
return <Base classNames={ getClassNames } style={ getStyle } { ...rest } />;
|
return <Base innerRef={ elementRef } classNames={ getClassNames } style={ getStyle } { ...rest } />;
|
||||||
}
|
};
|
@ -23,9 +23,7 @@ export const LayoutBadgeImageView: FC<LayoutBadgeImageViewProps> = props =>
|
|||||||
const newClassNames: string[] = [ 'badge-image' ];
|
const newClassNames: string[] = [ 'badge-image' ];
|
||||||
|
|
||||||
if(isGroup) newClassNames.push('group-badge');
|
if(isGroup) newClassNames.push('group-badge');
|
||||||
|
|
||||||
if(isGrayscale) newClassNames.push('grayscale');
|
if(isGrayscale) newClassNames.push('grayscale');
|
||||||
|
|
||||||
if(classNames.length) newClassNames.push(...classNames);
|
if(classNames.length) newClassNames.push(...classNames);
|
||||||
|
|
||||||
return newClassNames;
|
return newClassNames;
|
||||||
@ -37,16 +35,16 @@ export const LayoutBadgeImageView: FC<LayoutBadgeImageViewProps> = props =>
|
|||||||
|
|
||||||
if(imageElement)
|
if(imageElement)
|
||||||
{
|
{
|
||||||
newStyle.backgroundImage = `url(${ (isGroup) ? imageElement.src : GetConfiguration<string>('badge.asset.url').replace('%badgename%', badgeCode.toString())})`;
|
const badgeUrl = isGroup ? imageElement.src : GetConfiguration<string>('badge.asset.url', '').replace('%badgename%', badgeCode.toString());
|
||||||
|
|
||||||
|
newStyle.backgroundImage = `url(${ badgeUrl })`;
|
||||||
newStyle.width = imageElement.width;
|
newStyle.width = imageElement.width;
|
||||||
newStyle.height = imageElement.height;
|
newStyle.height = imageElement.height;
|
||||||
|
|
||||||
if(scale !== 1)
|
if(scale !== 1)
|
||||||
{
|
{
|
||||||
newStyle.transform = `scale(${ scale })`;
|
newStyle.transform = `scale(${ scale })`;
|
||||||
|
|
||||||
if(!(scale % 1)) newStyle.imageRendering = 'pixelated';
|
if(!(scale % 1)) newStyle.imageRendering = 'pixelated';
|
||||||
|
|
||||||
newStyle.width = (imageElement.width * scale);
|
newStyle.width = (imageElement.width * scale);
|
||||||
newStyle.height = (imageElement.height * scale);
|
newStyle.height = (imageElement.height * scale);
|
||||||
}
|
}
|
||||||
@ -55,37 +53,81 @@ export const LayoutBadgeImageView: FC<LayoutBadgeImageViewProps> = props =>
|
|||||||
if(Object.keys(style).length) newStyle = { ...newStyle, ...style };
|
if(Object.keys(style).length) newStyle = { ...newStyle, ...style };
|
||||||
|
|
||||||
return newStyle;
|
return newStyle;
|
||||||
}, [ imageElement, scale, style ]);
|
}, [ badgeCode, imageElement, isGroup, scale, style ]);
|
||||||
|
|
||||||
useEffect(() =>
|
useEffect(() =>
|
||||||
{
|
{
|
||||||
if(!badgeCode || !badgeCode.length) return;
|
|
||||||
|
if(!badgeCode || !badgeCode.length)
|
||||||
|
{
|
||||||
|
console.warn('LayoutBadgeImageView: Invalid or empty badgeCode', badgeCode);
|
||||||
|
setImageElement(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let didSetBadge = false;
|
let didSetBadge = false;
|
||||||
|
|
||||||
const onBadgeImageReadyEvent = (event: BadgeImageReadyEvent) =>
|
const onBadgeImageReadyEvent = async (event: BadgeImageReadyEvent) =>
|
||||||
{
|
{
|
||||||
if(event.badgeId !== badgeCode) return;
|
if(event.badgeId !== badgeCode) return;
|
||||||
|
|
||||||
const element = TextureUtils.generateImage(new NitroSprite(event.image));
|
try
|
||||||
|
{
|
||||||
element.onload = () => setImageElement(element);
|
const sprite = new NitroSprite(event.image);
|
||||||
|
const element = await TextureUtils.generateImage(sprite);
|
||||||
|
|
||||||
|
if(element && element.src && element.src.startsWith('data:image/'))
|
||||||
|
{
|
||||||
|
setImageElement(element);
|
||||||
didSetBadge = true;
|
didSetBadge = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
console.warn('LayoutBadgeImageView: Invalid badge image (event)', element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(error)
|
||||||
|
{
|
||||||
|
console.warn('LayoutBadgeImageView: Error generating badge image (event)', error);
|
||||||
|
}
|
||||||
|
|
||||||
GetSessionDataManager().events.removeEventListener(BadgeImageReadyEvent.IMAGE_READY, onBadgeImageReadyEvent);
|
GetSessionDataManager().events.removeEventListener(BadgeImageReadyEvent.IMAGE_READY, onBadgeImageReadyEvent);
|
||||||
}
|
};
|
||||||
|
|
||||||
GetSessionDataManager().events.addEventListener(BadgeImageReadyEvent.IMAGE_READY, onBadgeImageReadyEvent);
|
GetSessionDataManager().events.addEventListener(BadgeImageReadyEvent.IMAGE_READY, onBadgeImageReadyEvent);
|
||||||
|
|
||||||
|
const loadBadgeImage = async () =>
|
||||||
|
{
|
||||||
const texture = isGroup ? GetSessionDataManager().getGroupBadgeImage(badgeCode) : GetSessionDataManager().getBadgeImage(badgeCode);
|
const texture = isGroup ? GetSessionDataManager().getGroupBadgeImage(badgeCode) : GetSessionDataManager().getBadgeImage(badgeCode);
|
||||||
|
|
||||||
if(texture && !didSetBadge)
|
if(texture && !didSetBadge)
|
||||||
{
|
{
|
||||||
const element = TextureUtils.generateImage(new NitroSprite(texture));
|
try
|
||||||
|
{
|
||||||
|
const sprite = new NitroSprite(texture);
|
||||||
|
const element = await TextureUtils.generateImage(sprite);
|
||||||
|
|
||||||
element.onload = () => setImageElement(element);
|
if(element && element.src && element.src.startsWith('data:image/'))
|
||||||
|
{
|
||||||
|
setImageElement(element);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
console.warn('LayoutBadgeImageView: Invalid badge image (direct)', element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(error)
|
||||||
|
{
|
||||||
|
console.warn('LayoutBadgeImageView: Error generating badge image (direct)', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
console.log('LayoutBadgeImageView: No texture found for badge', badgeCode);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
loadBadgeImage();
|
||||||
|
|
||||||
return () => GetSessionDataManager().events.removeEventListener(BadgeImageReadyEvent.IMAGE_READY, onBadgeImageReadyEvent);
|
return () => GetSessionDataManager().events.removeEventListener(BadgeImageReadyEvent.IMAGE_READY, onBadgeImageReadyEvent);
|
||||||
}, [ badgeCode, isGroup ]);
|
}, [ badgeCode, isGroup ]);
|
||||||
@ -100,4 +142,4 @@ export const LayoutBadgeImageView: FC<LayoutBadgeImageViewProps> = props =>
|
|||||||
{ children }
|
{ children }
|
||||||
</Base>
|
</Base>
|
||||||
);
|
);
|
||||||
}
|
};
|
@ -15,7 +15,7 @@ interface LayoutFurniImageViewProps extends BaseProps<HTMLDivElement>
|
|||||||
|
|
||||||
export const LayoutFurniImageView: FC<LayoutFurniImageViewProps> = props =>
|
export const LayoutFurniImageView: FC<LayoutFurniImageViewProps> = props =>
|
||||||
{
|
{
|
||||||
const { productType = 's', productClassId = -1, direction = 2, extraData = '', scale = 1, style = {}, ...rest } = props;
|
const { productType = 's', productClassId = -1, direction = 2, extraData = '', scale = 1, style = {}, classNames = [], ...rest } = props;
|
||||||
const [ imageElement, setImageElement ] = useState<HTMLImageElement>(null);
|
const [ imageElement, setImageElement ] = useState<HTMLImageElement>(null);
|
||||||
|
|
||||||
const getStyle = useMemo(() =>
|
const getStyle = useMemo(() =>
|
||||||
@ -24,15 +24,19 @@ export const LayoutFurniImageView: FC<LayoutFurniImageViewProps> = props =>
|
|||||||
|
|
||||||
if(imageElement?.src?.length)
|
if(imageElement?.src?.length)
|
||||||
{
|
{
|
||||||
|
console.log('LayoutFurniImageView: Applying image URL', imageElement.src);
|
||||||
newStyle.backgroundImage = `url('${ imageElement.src }')`;
|
newStyle.backgroundImage = `url('${ imageElement.src }')`;
|
||||||
newStyle.width = imageElement.width;
|
newStyle.width = imageElement.width;
|
||||||
newStyle.height = imageElement.height;
|
newStyle.height = imageElement.height;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
console.log('LayoutFurniImageView: No imageElement, skipping style');
|
||||||
|
}
|
||||||
|
|
||||||
if(scale !== 1)
|
if(scale !== 1)
|
||||||
{
|
{
|
||||||
newStyle.transform = `scale(${ scale })`;
|
newStyle.transform = `scale(${ scale })`;
|
||||||
|
|
||||||
if(!(scale % 1)) newStyle.imageRendering = 'pixelated';
|
if(!(scale % 1)) newStyle.imageRendering = 'pixelated';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,40 +47,111 @@ export const LayoutFurniImageView: FC<LayoutFurniImageViewProps> = props =>
|
|||||||
|
|
||||||
useEffect(() =>
|
useEffect(() =>
|
||||||
{
|
{
|
||||||
|
console.log('LayoutFurniImageView: productType:', productType, 'productClassId:', productClassId, 'direction:', direction, 'extraData:', extraData);
|
||||||
|
|
||||||
|
if(productClassId < 0 || !productType)
|
||||||
|
{
|
||||||
|
console.warn('LayoutFurniImageView: Invalid productClassId or productType', { productClassId, productType });
|
||||||
|
setImageElement(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let imageResult: ImageResult = null;
|
let imageResult: ImageResult = null;
|
||||||
|
|
||||||
const listener: IGetImageListener = {
|
const listener: IGetImageListener = {
|
||||||
imageReady: (id, texture, image) =>
|
imageReady: async (id, texture, image) =>
|
||||||
|
{
|
||||||
|
console.log('LayoutFurniImageView: imageReady called', { id, texture, image });
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
if(!image && texture)
|
if(!image && texture)
|
||||||
{
|
{
|
||||||
image = TextureUtils.generateImage(texture);
|
image = await TextureUtils.generateImage(texture);
|
||||||
|
console.log('LayoutFurniImageView: Generated image from texture', image?.src);
|
||||||
}
|
}
|
||||||
|
|
||||||
image.onload = () => setImageElement(image);
|
if(image && image.src && image.src.startsWith('data:image/'))
|
||||||
|
{
|
||||||
|
image.onload = () => {
|
||||||
|
console.log('LayoutFurniImageView: Image loaded', image.src);
|
||||||
|
setImageElement(image);
|
||||||
|
};
|
||||||
|
if(image.complete) {
|
||||||
|
console.log('LayoutFurniImageView: Image already complete', image.src);
|
||||||
|
setImageElement(image);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
console.warn('LayoutFurniImageView: Invalid image in imageReady', image);
|
||||||
|
setImageElement(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(error)
|
||||||
|
{
|
||||||
|
console.warn('LayoutFurniImageView: Error in imageReady', error);
|
||||||
|
setImageElement(null);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
imageFailed: null
|
imageFailed: (id) => {
|
||||||
|
console.warn('LayoutFurniImageView: Image fetch failed for id', id);
|
||||||
|
setImageElement(null);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
switch(productType.toLocaleLowerCase())
|
try
|
||||||
|
{
|
||||||
|
switch(productType.toLowerCase())
|
||||||
{
|
{
|
||||||
case ProductTypeEnum.FLOOR:
|
case ProductTypeEnum.FLOOR:
|
||||||
|
console.log('LayoutFurniImageView: Fetching floor furniture image');
|
||||||
imageResult = GetRoomEngine().getFurnitureFloorImage(productClassId, new Vector3d(direction), 64, listener, 0, extraData);
|
imageResult = GetRoomEngine().getFurnitureFloorImage(productClassId, new Vector3d(direction), 64, listener, 0, extraData);
|
||||||
break;
|
break;
|
||||||
case ProductTypeEnum.WALL:
|
case ProductTypeEnum.WALL:
|
||||||
|
console.log('LayoutFurniImageView: Fetching wall furniture image');
|
||||||
imageResult = GetRoomEngine().getFurnitureWallImage(productClassId, new Vector3d(direction), 64, listener, 0, extraData);
|
imageResult = GetRoomEngine().getFurnitureWallImage(productClassId, new Vector3d(direction), 64, listener, 0, extraData);
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
console.warn('LayoutFurniImageView: Unknown productType', productType);
|
||||||
|
setImageElement(null);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(imageResult)
|
if(imageResult)
|
||||||
{
|
{
|
||||||
const image = imageResult.getImage();
|
const image = imageResult.getImage();
|
||||||
|
console.log('LayoutFurniImageView: Immediate imageResult', image?.src);
|
||||||
|
|
||||||
image.onload = () => setImageElement(image);
|
if(image && image.src && image.src.startsWith('data:image/'))
|
||||||
|
{
|
||||||
|
image.onload = () => {
|
||||||
|
console.log('LayoutFurniImageView: Immediate image loaded', image.src);
|
||||||
|
setImageElement(image);
|
||||||
|
};
|
||||||
|
if(image.complete) {
|
||||||
|
console.log('LayoutFurniImageView: Immediate image already complete', image.src);
|
||||||
|
setImageElement(image);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
console.warn('LayoutFurniImageView: No imageResult returned');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(error)
|
||||||
|
{
|
||||||
|
console.warn('LayoutFurniImageView: Error fetching image', error);
|
||||||
|
setImageElement(null);
|
||||||
}
|
}
|
||||||
}, [ productType, productClassId, direction, extraData ]);
|
}, [ productType, productClassId, direction, extraData ]);
|
||||||
|
|
||||||
if(!imageElement) return null;
|
if(!imageElement)
|
||||||
|
{
|
||||||
|
console.log('LayoutFurniImageView: Skipping render, no imageElement');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return <Base classNames={ [ 'furni-image' ] } style={ getStyle } { ...rest } />;
|
return <Base classNames={ [ 'furni-image', ...classNames ] } style={ getStyle } { ...rest } />;
|
||||||
}
|
};
|
@ -14,41 +14,44 @@ interface LayoutPetImageViewProps extends BaseProps<HTMLDivElement>
|
|||||||
headOnly?: boolean;
|
headOnly?: boolean;
|
||||||
direction?: number;
|
direction?: number;
|
||||||
scale?: number;
|
scale?: number;
|
||||||
|
isIcon?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LayoutPetImageView: FC<LayoutPetImageViewProps> = props =>
|
export const LayoutPetImageView: FC<LayoutPetImageViewProps> = props =>
|
||||||
{
|
{
|
||||||
const { figure = '', typeId = -1, paletteId = -1, petColor = 0xFFFFFF, customParts = [], posture = 'std', headOnly = false, direction = 0, scale = 1, style = {}, ...rest } = props;
|
const { figure = '', typeId = -1, paletteId = -1, petColor = 0xFFFFFF, customParts = [], posture = 'std', headOnly = false, direction = 0, scale = 1, isIcon = false, style = {}, classNames = [], ...rest } = props;
|
||||||
const [ petUrl, setPetUrl ] = useState<string>(null);
|
const [ petUrl, setPetUrl ] = useState<string>(null);
|
||||||
const [ width, setWidth ] = useState(0);
|
const [ width, setWidth ] = useState(0);
|
||||||
const [ height, setHeight ] = useState(0);
|
const [ height, setHeight ] = useState(0);
|
||||||
const isDisposed = useRef(false);
|
const isDisposed = useRef(false);
|
||||||
|
const retryCount = useRef(0);
|
||||||
|
const maxRetries = 3;
|
||||||
|
const prevFigure = useRef<string>('');
|
||||||
|
|
||||||
const getStyle = useMemo(() =>
|
const getStyle = useMemo(() =>
|
||||||
{
|
{
|
||||||
let newStyle: CSSProperties = {};
|
let newStyle: CSSProperties = {};
|
||||||
|
|
||||||
if(petUrl && petUrl.length) newStyle.backgroundImage = `url(${ petUrl })`;
|
if(petUrl && petUrl.length)
|
||||||
|
{
|
||||||
|
newStyle.backgroundImage = `url(${ petUrl })`;
|
||||||
|
newStyle.width = width;
|
||||||
|
newStyle.height = height;
|
||||||
|
}
|
||||||
|
|
||||||
if(scale !== 1)
|
if(scale !== 1)
|
||||||
{
|
{
|
||||||
newStyle.transform = `scale(${ scale })`;
|
newStyle.transform = `scale(${ scale })`;
|
||||||
|
|
||||||
if(!(scale % 1)) newStyle.imageRendering = 'pixelated';
|
if(!(scale % 1)) newStyle.imageRendering = 'pixelated';
|
||||||
}
|
}
|
||||||
|
|
||||||
newStyle.width = width;
|
|
||||||
newStyle.height = height;
|
|
||||||
|
|
||||||
if(Object.keys(style).length) newStyle = { ...newStyle, ...style };
|
if(Object.keys(style).length) newStyle = { ...newStyle, ...style };
|
||||||
|
|
||||||
return newStyle;
|
return newStyle;
|
||||||
}, [ petUrl, scale, style, width, height ]);
|
}, [ petUrl, scale, style, width, height, isIcon ]);
|
||||||
|
|
||||||
useEffect(() =>
|
const fetchPetImage = () =>
|
||||||
{
|
{
|
||||||
let url = null;
|
|
||||||
|
|
||||||
let petTypeId = typeId;
|
let petTypeId = typeId;
|
||||||
let petPaletteId = paletteId;
|
let petPaletteId = paletteId;
|
||||||
let petColor1 = petColor;
|
let petColor1 = petColor;
|
||||||
@ -56,66 +59,178 @@ export const LayoutPetImageView: FC<LayoutPetImageViewProps> = props =>
|
|||||||
let petHeadOnly = headOnly;
|
let petHeadOnly = headOnly;
|
||||||
|
|
||||||
if(figure && figure.length)
|
if(figure && figure.length)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
const petFigureData = new PetFigureData(figure);
|
const petFigureData = new PetFigureData(figure);
|
||||||
|
|
||||||
petTypeId = petFigureData.typeId;
|
petTypeId = petFigureData.typeId;
|
||||||
petPaletteId = petFigureData.paletteId;
|
petPaletteId = petFigureData.paletteId;
|
||||||
petColor1 = petFigureData.color;
|
petColor1 = petFigureData.color;
|
||||||
petCustomParts = petFigureData.customParts;
|
petCustomParts = petFigureData.customParts;
|
||||||
}
|
}
|
||||||
|
catch(error)
|
||||||
|
{
|
||||||
|
console.warn(`LayoutPetImageView: Error parsing PetFigureData (isIcon: ${isIcon})`, error, 'Figure:', figure);
|
||||||
|
setPetUrl(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(petTypeId === 16) petHeadOnly = false;
|
if(petTypeId === 16) petHeadOnly = false;
|
||||||
|
|
||||||
const imageResult = GetRoomEngine().getRoomObjectPetImage(petTypeId, petPaletteId, petColor1, new Vector3d((direction * 45)), 64, {
|
if(petTypeId < 0 || petPaletteId < 0)
|
||||||
imageReady: (id, texture, image) =>
|
{
|
||||||
|
console.warn(`LayoutPetImageView: Invalid petTypeId or petPaletteId (isIcon: ${isIcon})`, { petTypeId, petPaletteId, figure });
|
||||||
|
setPetUrl(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const imageResult = GetRoomEngine().getRoomObjectPetImage(petTypeId, petPaletteId, petColor1, new Vector3d((direction * 45)), isIcon ? 32 : 64, {
|
||||||
|
imageReady: async (id, texture, image) =>
|
||||||
{
|
{
|
||||||
if(isDisposed.current) return;
|
if(isDisposed.current) return;
|
||||||
|
|
||||||
if(image)
|
try
|
||||||
|
{
|
||||||
|
if(image && image.src && image.src.startsWith('data:image/'))
|
||||||
|
{
|
||||||
|
image.onload = () => {
|
||||||
|
setPetUrl(image.src);
|
||||||
|
setWidth(image.width);
|
||||||
|
setHeight(image.height);
|
||||||
|
};
|
||||||
|
if(image.complete)
|
||||||
{
|
{
|
||||||
setPetUrl(image.src);
|
setPetUrl(image.src);
|
||||||
setWidth(image.width);
|
setWidth(image.width);
|
||||||
setHeight(image.height);
|
setHeight(image.height);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else if(texture)
|
else if(texture)
|
||||||
{
|
{
|
||||||
setPetUrl(TextureUtils.generateImageUrl(texture));
|
const url = await TextureUtils.generateImageUrl(texture);
|
||||||
setWidth(texture.width);
|
if(url && url.startsWith('data:image/'))
|
||||||
setHeight(texture.height);
|
{
|
||||||
|
setPetUrl(url);
|
||||||
|
setWidth(texture.width || (isIcon ? 32 : 64));
|
||||||
|
setHeight(texture.height || (isIcon ? 32 : 64));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
console.warn(`LayoutPetImageView: Invalid texture URL (isIcon: ${isIcon})`, url);
|
||||||
|
setPetUrl(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
console.warn(`LayoutPetImageView: No image or texture in imageReady (isIcon: ${isIcon})`, { id });
|
||||||
|
setPetUrl(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(error)
|
||||||
|
{
|
||||||
|
console.warn(`LayoutPetImageView: Error in imageReady (isIcon: ${isIcon})`, error, 'ID:', id);
|
||||||
|
setPetUrl(null);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
imageFailed: (id) =>
|
imageFailed: (id) =>
|
||||||
{
|
{
|
||||||
|
console.warn(`LayoutPetImageView: Image fetch failed for id (isIcon: ${isIcon})`, id, 'Props:', { petTypeId, petPaletteId, posture });
|
||||||
|
if(retryCount.current < maxRetries)
|
||||||
|
{
|
||||||
|
retryCount.current += 1;
|
||||||
|
console.log(`LayoutPetImageView: Retrying fetch (retry: ${retryCount.current}/${maxRetries})`);
|
||||||
|
setTimeout(fetchPetImage, 1000);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
setPetUrl(null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, petHeadOnly, 0, petCustomParts, posture);
|
}, petHeadOnly, 0, petCustomParts, posture);
|
||||||
|
|
||||||
if(imageResult)
|
if(imageResult)
|
||||||
{
|
{
|
||||||
const image = imageResult.getImage();
|
(async () =>
|
||||||
|
{
|
||||||
|
const image = await imageResult.getImage();
|
||||||
|
|
||||||
if(image)
|
if(image && image.src && image.src.startsWith('data:image/'))
|
||||||
|
{
|
||||||
|
image.onload = () => {
|
||||||
|
setPetUrl(image.src);
|
||||||
|
setWidth(image.width);
|
||||||
|
setHeight(image.height);
|
||||||
|
};
|
||||||
|
if(image.complete)
|
||||||
{
|
{
|
||||||
setPetUrl(image.src);
|
setPetUrl(image.src);
|
||||||
setWidth(image.width);
|
setWidth(image.width);
|
||||||
setHeight(image.height);
|
setHeight(image.height);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [ figure, typeId, paletteId, petColor, customParts, posture, headOnly, direction ]);
|
})();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if(retryCount.current < maxRetries)
|
||||||
|
{
|
||||||
|
retryCount.current += 1;
|
||||||
|
console.log(`LayoutPetImageView: Retrying fetch (retry: ${retryCount.current}/${maxRetries})`);
|
||||||
|
setTimeout(fetchPetImage, 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(error)
|
||||||
|
{
|
||||||
|
if(retryCount.current < maxRetries)
|
||||||
|
{
|
||||||
|
retryCount.current += 1;
|
||||||
|
console.log(`LayoutPetImageView: Retrying fetch (retry: ${retryCount.current}/${maxRetries})`);
|
||||||
|
setTimeout(fetchPetImage, 1000);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
setPetUrl(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() =>
|
useEffect(() =>
|
||||||
{
|
{
|
||||||
isDisposed.current = false;
|
isDisposed.current = false;
|
||||||
|
retryCount.current = 0;
|
||||||
|
|
||||||
|
// Force reload if figure changes
|
||||||
|
const figureChanged = prevFigure.current !== figure;
|
||||||
|
if(figureChanged)
|
||||||
|
{
|
||||||
|
setPetUrl(null);
|
||||||
|
prevFigure.current = figure;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchPetImage();
|
||||||
|
|
||||||
|
// Handle visibility changes
|
||||||
|
const handleVisibilityChange = () =>
|
||||||
|
{
|
||||||
|
if(document.visibilityState === 'visible')
|
||||||
|
{
|
||||||
|
setPetUrl(null); // Reset to force reload
|
||||||
|
fetchPetImage();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener('visibilitychange', handleVisibilityChange);
|
||||||
|
|
||||||
return () =>
|
return () =>
|
||||||
{
|
{
|
||||||
isDisposed.current = true;
|
isDisposed.current = true;
|
||||||
}
|
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
||||||
}, []);
|
};
|
||||||
|
}, [ figure, typeId, paletteId, petColor, customParts, posture, headOnly, direction, isIcon ]);
|
||||||
|
|
||||||
const url = `url('${ petUrl }')`;
|
return <Base classNames={ [ 'pet-image', isIcon ? 'pet-icon' : '', ...classNames ] } style={ getStyle } { ...rest } />;
|
||||||
|
};
|
||||||
return <Base classNames={ [ 'pet-image' ] } style={ getStyle } { ...rest } />;
|
|
||||||
}
|
|
@ -24,25 +24,47 @@ export const LayoutRoomPreviewerView: FC<LayoutRoomPreviewerViewProps> = props =
|
|||||||
|
|
||||||
useEffect(() =>
|
useEffect(() =>
|
||||||
{
|
{
|
||||||
if(!roomPreviewer) return;
|
if(!roomPreviewer || height <= 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const update = (time: number) =>
|
const update = async (time: number) =>
|
||||||
{
|
{
|
||||||
if(!roomPreviewer || !renderingCanvas || !elementRef.current) return;
|
if(!roomPreviewer || !renderingCanvas || !elementRef.current) return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
roomPreviewer.updatePreviewRoomView();
|
roomPreviewer.updatePreviewRoomView();
|
||||||
|
|
||||||
if(!renderingCanvas.canvasUpdated) return;
|
if(!renderingCanvas.canvasUpdated) return;
|
||||||
|
|
||||||
elementRef.current.style.backgroundImage = `url(${ TextureUtils.generateImageUrl(renderingCanvas.master) })`;
|
const imageUrl = await TextureUtils.generateImageUrl(renderingCanvas.master);
|
||||||
|
|
||||||
|
if(imageUrl && imageUrl.startsWith('data:image/'))
|
||||||
|
{
|
||||||
|
elementRef.current.style.backgroundImage = `url(${ imageUrl })`;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
console.warn('LayoutRoomPreviewerView: Invalid image URL', imageUrl);
|
||||||
|
elementRef.current.style.backgroundImage = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(error)
|
||||||
|
{
|
||||||
|
console.warn('LayoutRoomPreviewerView: Error updating preview', error);
|
||||||
|
elementRef.current.style.backgroundImage = '';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!renderingCanvas)
|
if(!renderingCanvas)
|
||||||
{
|
{
|
||||||
if(elementRef.current && roomPreviewer)
|
if(elementRef.current && roomPreviewer)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
const computed = document.defaultView.getComputedStyle(elementRef.current, null);
|
const computed = document.defaultView.getComputedStyle(elementRef.current, null);
|
||||||
|
|
||||||
let backgroundColor = computed.backgroundColor;
|
let backgroundColor = computed.backgroundColor;
|
||||||
|
|
||||||
backgroundColor = ColorConverter.rgbStringToHex(backgroundColor);
|
backgroundColor = ColorConverter.rgbStringToHex(backgroundColor);
|
||||||
@ -56,12 +78,26 @@ export const LayoutRoomPreviewerView: FC<LayoutRoomPreviewerViewProps> = props =
|
|||||||
|
|
||||||
const canvas = roomPreviewer.getRenderingCanvas();
|
const canvas = roomPreviewer.getRenderingCanvas();
|
||||||
|
|
||||||
|
if(canvas)
|
||||||
|
{
|
||||||
setRenderingCanvas(canvas);
|
setRenderingCanvas(canvas);
|
||||||
|
|
||||||
canvas.canvasUpdated = true;
|
canvas.canvasUpdated = true;
|
||||||
|
|
||||||
update(-1);
|
update(-1);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
console.warn('LayoutRoomPreviewerView: Failed to initialize canvas');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(error)
|
||||||
|
{
|
||||||
|
console.warn('LayoutRoomPreviewerView: Error initializing canvas', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
console.warn('LayoutRoomPreviewerView: Missing elementRef or roomPreviewer');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GetTicker().add(update);
|
GetTicker().add(update);
|
||||||
@ -72,9 +108,15 @@ export const LayoutRoomPreviewerView: FC<LayoutRoomPreviewerViewProps> = props =
|
|||||||
|
|
||||||
const width = elementRef.current.parentElement.offsetWidth;
|
const width = elementRef.current.parentElement.offsetWidth;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
roomPreviewer.modifyRoomCanvas(width, height);
|
roomPreviewer.modifyRoomCanvas(width, height);
|
||||||
|
|
||||||
update(-1);
|
update(-1);
|
||||||
|
}
|
||||||
|
catch(error)
|
||||||
|
{
|
||||||
|
console.warn('LayoutRoomPreviewerView: Error resizing canvas', error);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
resizeObserver.observe(elementRef.current);
|
resizeObserver.observe(elementRef.current);
|
||||||
@ -82,10 +124,8 @@ export const LayoutRoomPreviewerView: FC<LayoutRoomPreviewerViewProps> = props =
|
|||||||
return () =>
|
return () =>
|
||||||
{
|
{
|
||||||
resizeObserver.disconnect();
|
resizeObserver.disconnect();
|
||||||
|
|
||||||
GetTicker().remove(update);
|
GetTicker().remove(update);
|
||||||
}
|
}
|
||||||
|
|
||||||
}, [ renderingCanvas, roomPreviewer, elementRef, height ]);
|
}, [ renderingCanvas, roomPreviewer, elementRef, height ]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -94,4 +134,4 @@ export const LayoutRoomPreviewerView: FC<LayoutRoomPreviewerViewProps> = props =
|
|||||||
{ children }
|
{ children }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
@ -334,7 +334,7 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 125px;
|
bottom: -125px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
z-index: 4;
|
z-index: 4;
|
||||||
}
|
}
|
||||||
@ -359,7 +359,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.choose-clothing {
|
.choose-clothing {
|
||||||
width: 320px;
|
width: 360px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.color-picker-frame {
|
.color-picker-frame {
|
||||||
@ -477,8 +477,8 @@
|
|||||||
|
|
||||||
.avatar-parts {
|
.avatar-parts {
|
||||||
border: none !important;
|
border: none !important;
|
||||||
height: 42px;
|
height: 50px;
|
||||||
width: 42px;
|
width: 50px;
|
||||||
background-position: center;
|
background-position: center;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
border-radius: 2rem !important;
|
border-radius: 2rem !important;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { FC, useEffect, useState } from 'react';
|
import { FC, useEffect, useState, useRef } from 'react';
|
||||||
import { AvatarEditorGridPartItem, GetConfiguration } from '../../../../api';
|
import { AvatarEditorGridPartItem, GetConfiguration } from '../../../../api';
|
||||||
import { LayoutCurrencyIcon, LayoutGridItem, LayoutGridItemProps } from '../../../../common';
|
import { LayoutGridItem, LayoutGridItemProps } from '../../../../common';
|
||||||
import { AvatarEditorIcon } from '../AvatarEditorIcon';
|
import { AvatarEditorIcon } from '../AvatarEditorIcon';
|
||||||
|
|
||||||
export interface AvatarEditorFigureSetItemViewProps extends LayoutGridItemProps
|
export interface AvatarEditorFigureSetItemViewProps extends LayoutGridItemProps
|
||||||
@ -12,21 +12,104 @@ export const AvatarEditorFigureSetItemView: FC<AvatarEditorFigureSetItemViewProp
|
|||||||
{
|
{
|
||||||
const { partItem = null, children = null, ...rest } = props;
|
const { partItem = null, children = null, ...rest } = props;
|
||||||
const [ updateId, setUpdateId ] = useState(-1);
|
const [ updateId, setUpdateId ] = useState(-1);
|
||||||
|
const [ imageUrl, setImageUrl ] = useState<string | null>(null);
|
||||||
|
const [ isValid, setIsValid ] = useState<boolean>(true);
|
||||||
|
const isDisposed = useRef(false);
|
||||||
|
const retryCount = useRef(0);
|
||||||
|
const maxRetries = 1;
|
||||||
|
|
||||||
const hcDisabled = GetConfiguration<boolean>('hc.disabled', false);
|
const hcDisabled = GetConfiguration<boolean>('hc.disabled', false);
|
||||||
|
|
||||||
|
const loadPartImage = async () =>
|
||||||
|
{
|
||||||
|
if(isDisposed.current) return;
|
||||||
|
|
||||||
|
if(!partItem)
|
||||||
|
{
|
||||||
|
setImageUrl(null);
|
||||||
|
setIsValid(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let resolvedImageUrl: string | null = null;
|
||||||
|
|
||||||
|
if(partItem.imageUrl && typeof partItem.imageUrl === 'object' && 'then' in partItem.imageUrl)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
resolvedImageUrl = await partItem.imageUrl;
|
||||||
|
}
|
||||||
|
catch(error)
|
||||||
|
{
|
||||||
|
console.warn(`AvatarEditorFigureSetItemView: Failed to resolve imageUrl promise for item ${partItem.id}`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(typeof partItem.imageUrl === 'string')
|
||||||
|
{
|
||||||
|
resolvedImageUrl = partItem.imageUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!resolvedImageUrl || !resolvedImageUrl.startsWith('data:image/'))
|
||||||
|
{
|
||||||
|
if(retryCount.current < maxRetries)
|
||||||
|
{
|
||||||
|
retryCount.current += 1;
|
||||||
|
setTimeout(loadPartImage, 1500);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
setImageUrl(null);
|
||||||
|
setIsValid(false);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setImageUrl(resolvedImageUrl);
|
||||||
|
setIsValid(true);
|
||||||
|
retryCount.current = 0;
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() =>
|
useEffect(() =>
|
||||||
{
|
{
|
||||||
const rerender = () => setUpdateId(prevValue => (prevValue + 1));
|
isDisposed.current = false;
|
||||||
|
retryCount.current = 0;
|
||||||
|
setIsValid(true);
|
||||||
|
|
||||||
|
loadPartImage();
|
||||||
|
|
||||||
|
const rerender = () =>
|
||||||
|
{
|
||||||
|
setUpdateId(prevValue => (prevValue + 1));
|
||||||
|
loadPartImage();
|
||||||
|
};
|
||||||
|
|
||||||
partItem.notify = rerender;
|
partItem.notify = rerender;
|
||||||
|
|
||||||
return () => partItem.notify = null;
|
const handleVisibilityChange = () =>
|
||||||
|
{
|
||||||
|
if(document.visibilityState === 'visible')
|
||||||
|
{
|
||||||
|
setImageUrl(null);
|
||||||
|
setIsValid(true);
|
||||||
|
loadPartImage();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener('visibilitychange', handleVisibilityChange);
|
||||||
|
|
||||||
|
return () =>
|
||||||
|
{
|
||||||
|
isDisposed.current = true;
|
||||||
|
partItem.notify = null;
|
||||||
|
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
||||||
|
};
|
||||||
}, [ partItem ]);
|
}, [ partItem ]);
|
||||||
|
|
||||||
|
if(!isValid) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="avatar-container">
|
<div className="avatar-container">
|
||||||
<LayoutGridItem className={ `avatar-parts ${ partItem.isSelected ? 'part-selected' : '' }` } itemImage={ (partItem.isClear ? undefined : partItem.imageUrl) } { ...rest }>
|
<LayoutGridItem className={`avatar-parts ${partItem.isSelected ? 'part-selected' : ''}`} itemImage={partItem.isClear ? undefined : imageUrl} {...rest}>
|
||||||
{ !hcDisabled && partItem.isHC && <i className="icon hc-icon position-absolute" /> }
|
{ !hcDisabled && partItem.isHC && <i className="icon hc-icon position-absolute" /> }
|
||||||
{ partItem.isClear && <AvatarEditorIcon icon="clear" /> }
|
{ partItem.isClear && <AvatarEditorIcon icon="clear" /> }
|
||||||
{ partItem.isSellable && <AvatarEditorIcon icon="sellable" position="absolute" className="end-1 bottom-1" /> }
|
{ partItem.isSellable && <AvatarEditorIcon icon="sellable" position="absolute" className="end-1 bottom-1" /> }
|
||||||
@ -34,4 +117,4 @@ export const AvatarEditorFigureSetItemView: FC<AvatarEditorFigureSetItemViewProp
|
|||||||
</LayoutGridItem>
|
</LayoutGridItem>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
@ -1,5 +1,5 @@
|
|||||||
import { NitroRectangle, TextureUtils } from '@nitrots/nitro-renderer';
|
import { NitroRectangle, TextureUtils } from '@nitrots/nitro-renderer';
|
||||||
import { FC, useRef } from 'react';
|
import { FC, useRef, useState } from 'react';
|
||||||
import { FaTimes } from 'react-icons/fa';
|
import { FaTimes } from 'react-icons/fa';
|
||||||
import { CameraPicture, CreateLinkEvent, GetRoomEngine, GetRoomSession, LocalizeText, PlaySound, SoundNames } from '../../../api';
|
import { CameraPicture, CreateLinkEvent, GetRoomEngine, GetRoomSession, LocalizeText, PlaySound, SoundNames } from '../../../api';
|
||||||
import { Column, DraggableWindowCamera, Flex } from '../../../common';
|
import { Column, DraggableWindowCamera, Flex } from '../../../common';
|
||||||
@ -20,6 +20,7 @@ export const CameraWidgetCaptureView: FC<CameraWidgetCaptureViewProps> = props =
|
|||||||
const { cameraRoll = null, setCameraRoll = null, selectedPictureIndex = -1, setSelectedPictureIndex = null } = useCamera();
|
const { cameraRoll = null, setCameraRoll = null, selectedPictureIndex = -1, setSelectedPictureIndex = null } = useCamera();
|
||||||
const { simpleAlert = null } = useNotification();
|
const { simpleAlert = null } = useNotification();
|
||||||
const elementRef = useRef<HTMLDivElement>();
|
const elementRef = useRef<HTMLDivElement>();
|
||||||
|
const [ isCapturing, setIsCapturing ] = useState(false);
|
||||||
|
|
||||||
const selectedPicture = ((selectedPictureIndex > -1) ? cameraRoll[selectedPictureIndex] : null);
|
const selectedPicture = ((selectedPictureIndex > -1) ? cameraRoll[selectedPictureIndex] : null);
|
||||||
|
|
||||||
@ -32,7 +33,7 @@ export const CameraWidgetCaptureView: FC<CameraWidgetCaptureViewProps> = props =
|
|||||||
return new NitroRectangle(Math.floor(frameBounds.x), Math.floor(frameBounds.y), Math.floor(frameBounds.width), Math.floor(frameBounds.height));
|
return new NitroRectangle(Math.floor(frameBounds.x), Math.floor(frameBounds.y), Math.floor(frameBounds.width), Math.floor(frameBounds.height));
|
||||||
}
|
}
|
||||||
|
|
||||||
const takePicture = () =>
|
const takePicture = async () =>
|
||||||
{
|
{
|
||||||
if(selectedPictureIndex > -1)
|
if(selectedPictureIndex > -1)
|
||||||
{
|
{
|
||||||
@ -40,22 +41,39 @@ export const CameraWidgetCaptureView: FC<CameraWidgetCaptureViewProps> = props =
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setIsCapturing(true);
|
||||||
|
try
|
||||||
|
{
|
||||||
const texture = GetRoomEngine().createTextureFromRoom(GetRoomSession().roomId, 1, getCameraBounds());
|
const texture = GetRoomEngine().createTextureFromRoom(GetRoomSession().roomId, 1, getCameraBounds());
|
||||||
|
const imageUrl = await TextureUtils.generateImageUrl(texture);
|
||||||
|
|
||||||
|
if (!imageUrl || typeof imageUrl !== 'string' || !imageUrl.startsWith('data:image/')) {
|
||||||
|
simpleAlert(LocalizeText('camera.error.body'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const clone = [ ...cameraRoll ];
|
const clone = [ ...cameraRoll ];
|
||||||
|
|
||||||
if(clone.length >= CAMERA_ROLL_LIMIT)
|
if(clone.length >= CAMERA_ROLL_LIMIT)
|
||||||
{
|
{
|
||||||
simpleAlert(LocalizeText('camera.full.body'));
|
simpleAlert(LocalizeText('camera.full.body'));
|
||||||
|
|
||||||
clone.pop();
|
clone.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
PlaySound(SoundNames.CAMERA_SHUTTER);
|
PlaySound(SoundNames.CAMERA_SHUTTER);
|
||||||
clone.push(new CameraPicture(texture, TextureUtils.generateImageUrl(texture)));
|
clone.push(new CameraPicture(texture, imageUrl));
|
||||||
|
|
||||||
setCameraRoll(clone);
|
setCameraRoll(clone);
|
||||||
}
|
}
|
||||||
|
catch (error)
|
||||||
|
{
|
||||||
|
simpleAlert(LocalizeText('camera.error.body'));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
setIsCapturing(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DraggableWindowCamera uniqueKey="nitro-camera-capture">
|
<DraggableWindowCamera uniqueKey="nitro-camera-capture">
|
||||||
|
@ -46,15 +46,17 @@ export const CameraWidgetCheckoutView: FC<CameraWidgetCheckoutViewProps> = props
|
|||||||
useMessageEvent<CameraStorageUrlMessageEvent>(CameraStorageUrlMessageEvent, event =>
|
useMessageEvent<CameraStorageUrlMessageEvent>(CameraStorageUrlMessageEvent, event =>
|
||||||
{
|
{
|
||||||
const parser = event.getParser();
|
const parser = event.getParser();
|
||||||
|
const cameraUrl = GetConfiguration<string>('camera.url');
|
||||||
|
const fullUrl = cameraUrl + '/' + parser.url;
|
||||||
|
|
||||||
setPictureUrl(GetConfiguration<string>('camera.url') + '/' + parser.url);
|
setPictureUrl(fullUrl);
|
||||||
});
|
});
|
||||||
|
|
||||||
useMessageEvent<NotEnoughBalanceMessageEvent>(NotEnoughBalanceMessageEvent, event =>
|
useMessageEvent<NotEnoughBalanceMessageEvent>(NotEnoughBalanceMessageEvent, event =>
|
||||||
{
|
{
|
||||||
const parser = event.getParser();
|
const parser = event.getParser();
|
||||||
|
|
||||||
if (!parser) return null;
|
if (!parser) return;
|
||||||
|
|
||||||
if (parser.notEnoughCredits && !parser.notEnoughActivityPoints) simpleAlert(LocalizeText('catalog.alert.notenough.credits.description'), null, null, null, LocalizeText('catalog.alert.notenough.title'));
|
if (parser.notEnoughCredits && !parser.notEnoughActivityPoints) simpleAlert(LocalizeText('catalog.alert.notenough.credits.description'), null, null, null, LocalizeText('catalog.alert.notenough.title'));
|
||||||
|
|
||||||
@ -91,7 +93,6 @@ export const CameraWidgetCheckoutView: FC<CameraWidgetCheckoutViewProps> = props
|
|||||||
useEffect(() =>
|
useEffect(() =>
|
||||||
{
|
{
|
||||||
if(!base64Url) return;
|
if(!base64Url) return;
|
||||||
|
|
||||||
GetRoomEngine().saveBase64AsScreenshot(base64Url);
|
GetRoomEngine().saveBase64AsScreenshot(base64Url);
|
||||||
}, [ base64Url ]);
|
}, [ base64Url ]);
|
||||||
|
|
||||||
@ -102,12 +103,15 @@ export const CameraWidgetCheckoutView: FC<CameraWidgetCheckoutViewProps> = props
|
|||||||
<NitroCardHeaderView headerText={ LocalizeText('camera.confirm_phase.title') } onCloseClick={ event => processAction('close') } />
|
<NitroCardHeaderView headerText={ LocalizeText('camera.confirm_phase.title') } onCloseClick={ event => processAction('close') } />
|
||||||
<NitroCardContentView>
|
<NitroCardContentView>
|
||||||
<Flex center>
|
<Flex center>
|
||||||
{ (pictureUrl && pictureUrl.length) &&
|
{ (pictureUrl && pictureUrl.length) ? (
|
||||||
<LayoutImage className="picture-preview border" imageUrl={ pictureUrl } /> }
|
<LayoutImage className="picture-preview border" imageUrl={ pictureUrl } />
|
||||||
{ (!pictureUrl || !pictureUrl.length) &&
|
) : base64Url ? (
|
||||||
|
<LayoutImage className="picture-preview border" imageUrl={ base64Url } />
|
||||||
|
) : (
|
||||||
<Flex center className="picture-preview border">
|
<Flex center className="picture-preview border">
|
||||||
<Text bold>{ LocalizeText('camera.loading') }</Text>
|
<Text bold>{ LocalizeText('camera.loading') }</Text>
|
||||||
</Flex> }
|
</Flex>
|
||||||
|
) }
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex justifyContent="between" alignItems="center" className="bg-muted rounded p-2">
|
<Flex justifyContent="between" alignItems="center" className="bg-muted rounded p-2">
|
||||||
<Column size={ publishDisabled ? 10 : 6 } gap={ 1 }>
|
<Column size={ publishDisabled ? 10 : 6 } gap={ 1 }>
|
||||||
|
@ -39,23 +39,28 @@ export const CameraWidgetShowPhotoView: FC<CameraWidgetShowPhotoViewProps> = pro
|
|||||||
const getUserData = (roomId: number, objectId: number, type: string): number | string =>
|
const getUserData = (roomId: number, objectId: number, type: string): number | string =>
|
||||||
{
|
{
|
||||||
const roomObject = GetRoomEngine().getRoomObject(roomId, objectId, RoomObjectCategory.WALL);
|
const roomObject = GetRoomEngine().getRoomObject(roomId, objectId, RoomObjectCategory.WALL);
|
||||||
if (!roomObject) return;
|
if (!roomObject) return '';
|
||||||
return type == 'username' ? roomObject.model.getValue<number>(RoomObjectVariable.FURNITURE_OWNER_NAME) : roomObject.model.getValue<number>(RoomObjectVariable.FURNITURE_OWNER_ID);
|
return type === 'username' ? roomObject.model.getValue<string>(RoomObjectVariable.FURNITURE_OWNER_NAME) : roomObject.model.getValue<number>(RoomObjectVariable.FURNITURE_OWNER_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => { setImageIndex(currentIndex); }, [ currentIndex ]);
|
useEffect(() =>
|
||||||
|
{
|
||||||
|
setImageIndex(currentIndex);
|
||||||
|
}, [ currentIndex, currentPhotos ]);
|
||||||
|
|
||||||
if(!currentImage) return null;
|
if(!currentImage) return null;
|
||||||
|
|
||||||
|
const imageUrl = currentImage.w || '';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid style={ { display: 'flex', flexDirection: 'column' } }>
|
<Grid style={ { display: 'flex', flexDirection: 'column' } }>
|
||||||
<Flex center className="picture-preview border border-black" style={ currentImage.w ? { backgroundImage: 'url(' + currentImage.w + ')' } : {} }>
|
<Flex center className="picture-preview border border-black" style={ imageUrl ? { backgroundImage: `url(${imageUrl})` } : {} }>
|
||||||
{ !currentImage.w && <Text bold>{ LocalizeText('camera.loading') }</Text> }
|
{ !imageUrl && <Text bold>{ LocalizeText('camera.loading') }</Text> }
|
||||||
</Flex>
|
</Flex>
|
||||||
{ currentImage.m && currentImage.m.length && <Text center>{ currentImage.m }</Text> }
|
{ currentImage.m && currentImage.m.length && <Text center>{ currentImage.m }</Text> }
|
||||||
<Flex alignItems="center" justifyContent="between">
|
<Flex alignItems="center" justifyContent="between">
|
||||||
<Text> { new Date(currentImage.t * 1000).toLocaleDateString(undefined, { day: 'numeric', month: 'long', year: 'numeric' }) } </Text>
|
<Text> { new Date(currentImage.t * 1000).toLocaleDateString(undefined, { day: 'numeric', month: 'long', year: 'numeric' }) } </Text>
|
||||||
<Text className="username" onClick={() => GetUserProfile(Number(getUserData(currentImage.s, Number(currentImage.u), 'id')))}> { getUserData(currentImage.s, Number(currentImage.u), 'username') } </Text>
|
<Text className="username" onClick={() => GetUserProfile(Number(getUserData(currentImage.s, Number(currentImage.u), 'id')))}> { getUserData(currentImage.s, Number(currentImage.u), 'username') || 'Unknown' } </Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
{ (currentPhotos.length > 1) &&
|
{ (currentPhotos.length > 1) &&
|
||||||
<Flex className="picture-preview-buttons">
|
<Flex className="picture-preview-buttons">
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import { IRoomCameraWidgetEffect, IRoomCameraWidgetSelectedEffect, RoomCameraWidgetSelectedEffect } from '@nitrots/nitro-renderer';
|
import { IRoomCameraWidgetEffect, IRoomCameraWidgetSelectedEffect, RoomCameraWidgetSelectedEffect, TextureUtils, NitroSprite } from '@nitrots/nitro-renderer';
|
||||||
import { FC, useCallback, useEffect, useMemo, useState } from 'react';
|
import { FC, useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { FaSave, FaSearchMinus, FaSearchPlus, FaTrash } from 'react-icons/fa';
|
import { FaSave, FaSearchMinus, FaSearchPlus, FaTrash } from 'react-icons/fa';
|
||||||
import ReactSlider from 'react-slider';
|
import ReactSlider from 'react-slider';
|
||||||
import { CameraEditorTabs, CameraPicture, CameraPictureThumbnail, GetRoomCameraWidgetManager, LocalizeText } from '../../../../api';
|
import { CameraEditorTabs, CameraPicture, CameraPictureThumbnail, GetRoomCameraWidgetManager, LocalizeText } from '../../../../api';
|
||||||
import { Button, ButtonGroup, Column, Flex, Grid, LayoutImage, NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView, Slider, Text } from '../../../../common';
|
import { Button, ButtonGroup, Column, Flex, Grid, LayoutImage, NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView, Slider, Text } from '../../../../common';
|
||||||
import { CameraWidgetEffectListView } from './effect-list/CameraWidgetEffectListView';
|
import { CameraWidgetEffectListView } from './effect-list/CameraWidgetEffectListView';
|
||||||
|
import { ColorMatrixFilter } from '@pixi/filter-color-matrix';
|
||||||
|
|
||||||
export interface CameraWidgetEditorViewProps
|
export interface CameraWidgetEditorViewProps
|
||||||
{
|
{
|
||||||
@ -26,6 +27,7 @@ export const CameraWidgetEditorView: FC<CameraWidgetEditorViewProps> = props =>
|
|||||||
const [ selectedEffects, setSelectedEffects ] = useState<IRoomCameraWidgetSelectedEffect[]>([]);
|
const [ selectedEffects, setSelectedEffects ] = useState<IRoomCameraWidgetSelectedEffect[]>([]);
|
||||||
const [ effectsThumbnails, setEffectsThumbnails ] = useState<CameraPictureThumbnail[]>([]);
|
const [ effectsThumbnails, setEffectsThumbnails ] = useState<CameraPictureThumbnail[]>([]);
|
||||||
const [ isZoomed, setIsZoomed ] = useState(false);
|
const [ isZoomed, setIsZoomed ] = useState(false);
|
||||||
|
const [ currentPictureUrl, setCurrentPictureUrl ] = useState<string | null>(null);
|
||||||
|
|
||||||
const getColorMatrixEffects = useMemo(() =>
|
const getColorMatrixEffects = useMemo(() =>
|
||||||
{
|
{
|
||||||
@ -52,12 +54,12 @@ export const CameraWidgetEditorView: FC<CameraWidgetEditorViewProps> = props =>
|
|||||||
if(!name || !name.length || !selectedEffects || !selectedEffects.length) return -1;
|
if(!name || !name.length || !selectedEffects || !selectedEffects.length) return -1;
|
||||||
|
|
||||||
return selectedEffects.findIndex(effect => (effect.effect.name === name));
|
return selectedEffects.findIndex(effect => (effect.effect.name === name));
|
||||||
}, [ selectedEffects ])
|
}, [ selectedEffects ]);
|
||||||
|
|
||||||
const getCurrentEffectIndex = useMemo(() =>
|
const getCurrentEffectIndex = useMemo(() =>
|
||||||
{
|
{
|
||||||
return getSelectedEffectIndex(selectedEffectName)
|
return getSelectedEffectIndex(selectedEffectName);
|
||||||
}, [ selectedEffectName, getSelectedEffectIndex ])
|
}, [ selectedEffectName, getSelectedEffectIndex ]);
|
||||||
|
|
||||||
const getCurrentEffect = useMemo(() =>
|
const getCurrentEffect = useMemo(() =>
|
||||||
{
|
{
|
||||||
@ -83,9 +85,66 @@ export const CameraWidgetEditorView: FC<CameraWidgetEditorViewProps> = props =>
|
|||||||
});
|
});
|
||||||
}, [ getCurrentEffectIndex, setSelectedEffects ]);
|
}, [ getCurrentEffectIndex, setSelectedEffects ]);
|
||||||
|
|
||||||
const getCurrentPictureUrl = useMemo(() =>
|
const applyEffectsWithFallback = async (texture: RenderTexture, effects: IRoomCameraWidgetSelectedEffect[], isZoomed: boolean): Promise<string | null> =>
|
||||||
{
|
{
|
||||||
return GetRoomCameraWidgetManager().applyEffects(picture.texture, selectedEffects, isZoomed).src;
|
const result = GetRoomCameraWidgetManager().applyEffects(texture, effects, isZoomed);
|
||||||
|
let src = result?.src;
|
||||||
|
|
||||||
|
if (!src || typeof src !== 'string' || !src.startsWith('data:image/')) {
|
||||||
|
|
||||||
|
let sprite = new NitroSprite(texture);
|
||||||
|
let appliedEffects = false;
|
||||||
|
|
||||||
|
for (const selectedEffect of effects) {
|
||||||
|
const effect = selectedEffect.effect;
|
||||||
|
const alpha = selectedEffect.alpha;
|
||||||
|
|
||||||
|
if (effect.colorMatrix) {
|
||||||
|
const colorMatrixFilter = new ColorMatrixFilter();
|
||||||
|
colorMatrixFilter.matrix = effect.colorMatrix;
|
||||||
|
colorMatrixFilter.alpha = alpha;
|
||||||
|
sprite.filters = (sprite.filters || []).concat(colorMatrixFilter);
|
||||||
|
appliedEffects = true;
|
||||||
|
} else if (effect.texture) {
|
||||||
|
const overlaySprite = new NitroSprite(effect.texture);
|
||||||
|
overlaySprite.alpha = alpha;
|
||||||
|
overlaySprite.width = texture.width;
|
||||||
|
overlaySprite.height = texture.height;
|
||||||
|
|
||||||
|
const container = new NitroSprite();
|
||||||
|
container.addChild(sprite, overlaySprite);
|
||||||
|
|
||||||
|
sprite = container;
|
||||||
|
appliedEffects = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (appliedEffects) {
|
||||||
|
src = await TextureUtils.generateImageUrl(sprite);
|
||||||
|
} else {
|
||||||
|
src = await TextureUtils.generateImageUrl(texture);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!src || typeof src !== 'string' || !src.startsWith('data:image/')) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return src;
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() =>
|
||||||
|
{
|
||||||
|
if (!picture || !picture.texture) {
|
||||||
|
setCurrentPictureUrl(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
applyEffectsWithFallback(picture.texture, selectedEffects, isZoomed)
|
||||||
|
.then(url => setCurrentPictureUrl(url))
|
||||||
|
.catch(error => {
|
||||||
|
setCurrentPictureUrl(null);
|
||||||
|
});
|
||||||
}, [ picture, selectedEffects, isZoomed ]);
|
}, [ picture, selectedEffects, isZoomed ]);
|
||||||
|
|
||||||
const processAction = useCallback((type: string, effectName: string = null) =>
|
const processAction = useCallback((type: string, effectName: string = null) =>
|
||||||
@ -99,7 +158,9 @@ export const CameraWidgetEditorView: FC<CameraWidgetEditorViewProps> = props =>
|
|||||||
onCancel();
|
onCancel();
|
||||||
return;
|
return;
|
||||||
case 'checkout':
|
case 'checkout':
|
||||||
onCheckout(getCurrentPictureUrl);
|
if (currentPictureUrl) {
|
||||||
|
onCheckout(currentPictureUrl);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
case 'change_tab':
|
case 'change_tab':
|
||||||
setCurrentTab(String(effectName));
|
setCurrentTab(String(effectName));
|
||||||
@ -145,7 +206,7 @@ export const CameraWidgetEditorView: FC<CameraWidgetEditorViewProps> = props =>
|
|||||||
case 'download': {
|
case 'download': {
|
||||||
const image = new Image();
|
const image = new Image();
|
||||||
|
|
||||||
image.src = getCurrentPictureUrl
|
image.src = currentPictureUrl || '';
|
||||||
|
|
||||||
const newWindow = window.open('');
|
const newWindow = window.open('');
|
||||||
newWindow.document.write(image.outerHTML);
|
newWindow.document.write(image.outerHTML);
|
||||||
@ -155,18 +216,32 @@ export const CameraWidgetEditorView: FC<CameraWidgetEditorViewProps> = props =>
|
|||||||
setIsZoomed(!isZoomed);
|
setIsZoomed(!isZoomed);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}, [ isZoomed, availableEffects, selectedEffectName, getCurrentPictureUrl, getSelectedEffectIndex, onCancel, onCheckout, onClose, setIsZoomed, setSelectedEffects ]);
|
}, [ isZoomed, availableEffects, selectedEffectName, currentPictureUrl, getSelectedEffectIndex, onCancel, onCheckout, onClose, setIsZoomed, setSelectedEffects ]);
|
||||||
|
|
||||||
useEffect(() =>
|
useEffect(() =>
|
||||||
{
|
{
|
||||||
const thumbnails: CameraPictureThumbnail[] = [];
|
if (!picture || !picture.texture) {
|
||||||
|
setEffectsThumbnails([]);
|
||||||
for(const effect of availableEffects)
|
return;
|
||||||
{
|
|
||||||
thumbnails.push(new CameraPictureThumbnail(effect.name, GetRoomCameraWidgetManager().applyEffects(picture.texture, [ new RoomCameraWidgetSelectedEffect(effect, 1) ], false).src));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const thumbnails: CameraPictureThumbnail[] = [];
|
||||||
|
|
||||||
|
const generateThumbnails = async () =>
|
||||||
|
{
|
||||||
|
for(const effect of availableEffects)
|
||||||
|
{
|
||||||
|
const thumbnailSrc = await applyEffectsWithFallback(picture.texture, [ new RoomCameraWidgetSelectedEffect(effect, 1) ], false);
|
||||||
|
if (thumbnailSrc) {
|
||||||
|
thumbnails.push(new CameraPictureThumbnail(effect.name, thumbnailSrc));
|
||||||
|
}
|
||||||
|
}
|
||||||
setEffectsThumbnails(thumbnails);
|
setEffectsThumbnails(thumbnails);
|
||||||
|
};
|
||||||
|
|
||||||
|
generateThumbnails().catch(error => {
|
||||||
|
setEffectsThumbnails([]);
|
||||||
|
});
|
||||||
}, [ picture, availableEffects ]);
|
}, [ picture, availableEffects ]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -185,7 +260,11 @@ export const CameraWidgetEditorView: FC<CameraWidgetEditorViewProps> = props =>
|
|||||||
</Column>
|
</Column>
|
||||||
<Column size={ 7 } justifyContent="between" overflow="hidden">
|
<Column size={ 7 } justifyContent="between" overflow="hidden">
|
||||||
<Column center>
|
<Column center>
|
||||||
<LayoutImage imageUrl={ getCurrentPictureUrl } className="picture-preview" />
|
{ currentPictureUrl ? (
|
||||||
|
<LayoutImage imageUrl={ currentPictureUrl } className="picture-preview" />
|
||||||
|
) : (
|
||||||
|
<Text center bold>{ LocalizeText('camera.loading.error') }</Text>
|
||||||
|
) }
|
||||||
{ selectedEffectName &&
|
{ selectedEffectName &&
|
||||||
<Column center fullWidth gap={ 1 }>
|
<Column center fullWidth gap={ 1 }>
|
||||||
<Text>{ LocalizeText('camera.effect.name.' + selectedEffectName) }</Text>
|
<Text>{ LocalizeText('camera.effect.name.' + selectedEffectName) }</Text>
|
||||||
@ -195,7 +274,11 @@ export const CameraWidgetEditorView: FC<CameraWidgetEditorViewProps> = props =>
|
|||||||
step={ 0.01 }
|
step={ 0.01 }
|
||||||
value={ getCurrentEffect.alpha }
|
value={ getCurrentEffect.alpha }
|
||||||
onChange={ event => setSelectedEffectAlpha(event) }
|
onChange={ event => setSelectedEffectAlpha(event) }
|
||||||
renderThumb={ (props, state) => <div { ...props }>{ state.valueNow }</div> } />
|
renderThumb={ (props, state) => {
|
||||||
|
const { key, ...restProps } = props;
|
||||||
|
return <div key={ key } { ...restProps }>{ state.valueNow }</div>;
|
||||||
|
} }
|
||||||
|
/>
|
||||||
</Column> }
|
</Column> }
|
||||||
</Column>
|
</Column>
|
||||||
<Flex justifyContent="between">
|
<Flex justifyContent="between">
|
||||||
@ -203,7 +286,7 @@ export const CameraWidgetEditorView: FC<CameraWidgetEditorViewProps> = props =>
|
|||||||
<Button onClick={ event => processAction('clear_effects') }>
|
<Button onClick={ event => processAction('clear_effects') }>
|
||||||
<FaTrash className="fa-icon" />
|
<FaTrash className="fa-icon" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={ event => processAction('download') }>
|
<Button onClick={ event => processAction('download') } disabled={ !currentPictureUrl }>
|
||||||
<FaSave className="fa-icon" />
|
<FaSave className="fa-icon" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={ event => processAction('zoom') }>
|
<Button onClick={ event => processAction('zoom') }>
|
||||||
@ -215,7 +298,7 @@ export const CameraWidgetEditorView: FC<CameraWidgetEditorViewProps> = props =>
|
|||||||
<Button onClick={ event => processAction('cancel') }>
|
<Button onClick={ event => processAction('cancel') }>
|
||||||
{ LocalizeText('generic.cancel') }
|
{ LocalizeText('generic.cancel') }
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={ event => processAction('checkout') }>
|
<Button onClick={ event => processAction('checkout') } disabled={ !currentPictureUrl }>
|
||||||
{ LocalizeText('camera.preview.button.text') }
|
{ LocalizeText('camera.preview.button.text') }
|
||||||
</Button>
|
</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
@ -53,7 +53,7 @@ export const CatalogGridOfferView: FC<CatalogGridOfferViewProps> = props =>
|
|||||||
return (
|
return (
|
||||||
<LayoutGridItem itemImage={ iconUrl } itemCount={ ((offer.pricingModel === Offer.PRICING_MODEL_MULTI) ? product.productCount : 1) } itemUniqueSoldout={ (product.uniqueLimitedItemSeriesSize && !product.uniqueLimitedItemsLeft) } itemUniqueNumber={ product.uniqueLimitedItemSeriesSize } itemActive={ itemActive } onMouseDown={ onMouseEvent } onMouseUp={ onMouseEvent } onMouseOut={ onMouseEvent } { ...rest }>
|
<LayoutGridItem itemImage={ iconUrl } itemCount={ ((offer.pricingModel === Offer.PRICING_MODEL_MULTI) ? product.productCount : 1) } itemUniqueSoldout={ (product.uniqueLimitedItemSeriesSize && !product.uniqueLimitedItemsLeft) } itemUniqueNumber={ product.uniqueLimitedItemSeriesSize } itemActive={ itemActive } onMouseDown={ onMouseEvent } onMouseUp={ onMouseEvent } onMouseOut={ onMouseEvent } { ...rest }>
|
||||||
{ (offer.product.productType === ProductTypeEnum.ROBOT) &&
|
{ (offer.product.productType === ProductTypeEnum.ROBOT) &&
|
||||||
<LayoutAvatarImageView figure={ offer.product.extraParam } headOnly={ true } direction={ 3 } /> }
|
<LayoutAvatarImageView figure={ offer.product.extraParam } direction={ 2 } /> }
|
||||||
</LayoutGridItem>
|
</LayoutGridItem>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,24 +1,21 @@
|
|||||||
import { MouseEventType } from "@nitrots/nitro-renderer";
|
import { MouseEventType } from "@nitrots/nitro-renderer";
|
||||||
import { FC, MouseEvent, PropsWithChildren, useState } from "react";
|
import { FC, MouseEvent, PropsWithChildren, useState } from "react";
|
||||||
import {
|
import { attemptBotPlacement, IBotItem, UnseenItemCategory, } from "../../../../api";
|
||||||
attemptBotPlacement,
|
|
||||||
IBotItem,
|
|
||||||
UnseenItemCategory,
|
|
||||||
} from "../../../../api";
|
|
||||||
import { LayoutAvatarImageView, LayoutGridItem } from "../../../../common";
|
import { LayoutAvatarImageView, LayoutGridItem } from "../../../../common";
|
||||||
import { useInventoryBots, useInventoryUnseenTracker } from "../../../../hooks";
|
import { useInventoryBots, useInventoryUnseenTracker } from "../../../../hooks";
|
||||||
|
|
||||||
export const InventoryBotItemView: FC<
|
export const InventoryBotItemView: FC<PropsWithChildren<{ botItem: IBotItem }>> = props =>
|
||||||
PropsWithChildren<{ botItem: IBotItem }>
|
{
|
||||||
> = (props) => {
|
|
||||||
const { botItem = null, children = null, ...rest } = props;
|
const { botItem = null, children = null, ...rest } = props;
|
||||||
const [isMouseDown, setMouseDown] = useState(false);
|
const [ isMouseDown, setMouseDown ] = useState(false);
|
||||||
const { selectedBot = null, setSelectedBot = null } = useInventoryBots();
|
const { selectedBot = null, setSelectedBot = null } = useInventoryBots();
|
||||||
const { isUnseen = null } = useInventoryUnseenTracker();
|
const { isUnseen = null } = useInventoryUnseenTracker();
|
||||||
const unseen = isUnseen(UnseenItemCategory.BOT, botItem.botData.id);
|
const unseen = isUnseen(UnseenItemCategory.BOT, botItem.botData.id);
|
||||||
|
|
||||||
const onMouseEvent = (event: MouseEvent) => {
|
const onMouseEvent = (event: MouseEvent) =>
|
||||||
switch (event.type) {
|
{
|
||||||
|
switch(event.type)
|
||||||
|
{
|
||||||
case MouseEventType.MOUSE_DOWN:
|
case MouseEventType.MOUSE_DOWN:
|
||||||
setSelectedBot(botItem);
|
setSelectedBot(botItem);
|
||||||
setMouseDown(true);
|
setMouseDown(true);
|
||||||
@ -27,32 +24,20 @@ export const InventoryBotItemView: FC<
|
|||||||
setMouseDown(false);
|
setMouseDown(false);
|
||||||
return;
|
return;
|
||||||
case MouseEventType.ROLL_OUT:
|
case MouseEventType.ROLL_OUT:
|
||||||
if (!isMouseDown || selectedBot !== botItem) return;
|
if(!isMouseDown || (selectedBot !== botItem)) return;
|
||||||
|
|
||||||
attemptBotPlacement(botItem);
|
attemptBotPlacement(botItem);
|
||||||
return;
|
return;
|
||||||
case "dblclick":
|
case 'dblclick':
|
||||||
attemptBotPlacement(botItem);
|
attemptBotPlacement(botItem);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LayoutGridItem
|
<LayoutGridItem itemActive={ (selectedBot === botItem) } itemUnseen={ unseen } onMouseDown={ onMouseEvent } onMouseUp={ onMouseEvent } onMouseOut={ onMouseEvent } onDoubleClick={ onMouseEvent } { ...rest }>
|
||||||
itemActive={selectedBot === botItem}
|
<LayoutAvatarImageView figure={ botItem.botData.figure } direction={ 2 } />
|
||||||
itemUnseen={unseen}
|
{ children }
|
||||||
onMouseDown={onMouseEvent}
|
|
||||||
onMouseUp={onMouseEvent}
|
|
||||||
onMouseOut={onMouseEvent}
|
|
||||||
onDoubleClick={onMouseEvent}
|
|
||||||
{...rest}
|
|
||||||
>
|
|
||||||
<LayoutAvatarImageView
|
|
||||||
figure={botItem.botData.figure}
|
|
||||||
direction={3}
|
|
||||||
headOnly={true}
|
|
||||||
/>
|
|
||||||
{children}
|
|
||||||
</LayoutGridItem>
|
</LayoutGridItem>
|
||||||
);
|
);
|
||||||
};
|
};
|
@ -77,8 +77,7 @@
|
|||||||
color: #fff;
|
color: #fff;
|
||||||
width: 400px;
|
width: 400px;
|
||||||
min-height: 400px;
|
min-height: 400px;
|
||||||
max-height: 500px;
|
max-height: 400px;
|
||||||
overflow-y: hidden;
|
|
||||||
border: 2px solid #ffd700;
|
border: 2px solid #ffd700;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
box-shadow: 0 0 20px rgba(255, 215, 0, 0.5);
|
box-shadow: 0 0 20px rgba(255, 215, 0, 0.5);
|
||||||
@ -117,7 +116,7 @@
|
|||||||
padding: 20px;
|
padding: 20px;
|
||||||
position: relative;
|
position: relative;
|
||||||
height: calc(100% - 34px);
|
height: calc(100% - 34px);
|
||||||
overflow-y: hidden;
|
overflow-y: auto; /* Changed to auto to allow scrolling */
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
@ -125,12 +124,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.grid {
|
.grid {
|
||||||
animation: scroll-credits 20s linear infinite;
|
animation: scroll-credits 40s linear infinite;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
will-change: transform;
|
will-change: transform;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -196,18 +192,14 @@
|
|||||||
@keyframes scroll-credits {
|
@keyframes scroll-credits {
|
||||||
0% {
|
0% {
|
||||||
transform: translateY(100%);
|
transform: translateY(100%);
|
||||||
opacity: 0;
|
|
||||||
}
|
}
|
||||||
10% {
|
5% {
|
||||||
transform: translateY(80%);
|
transform: translateY(50%);
|
||||||
opacity: 1;
|
|
||||||
}
|
}
|
||||||
90% {
|
95% {
|
||||||
transform: translateY(-80%);
|
transform: translateY(-110%);
|
||||||
opacity: 1;
|
|
||||||
}
|
}
|
||||||
100% {
|
100% {
|
||||||
transform: translateY(-100%);
|
transform: translateY(-150%);
|
||||||
opacity: 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -57,18 +57,16 @@ export const NitroSystemAlertView: FC<NotificationDefaultAlertViewProps> = props
|
|||||||
<Text>- DuckieTM (Re-Design)</Text>
|
<Text>- DuckieTM (Re-Design)</Text>
|
||||||
<Text>- Jonas (Contributing)</Text>
|
<Text>- Jonas (Contributing)</Text>
|
||||||
<Text>- Ohlucas (Sunset resources)</Text>
|
<Text>- Ohlucas (Sunset resources)</Text>
|
||||||
<Text center bold small>v1.5.0</Text>
|
<Text center bold small>v2.0.0</Text>
|
||||||
<Button fullWidth onClick={event => window.open('https://github.com/duckietm/Nitro-Cool-UI')}>
|
<Button fullWidth onClick={event => window.open('https://github.com/duckietm/Nitro-Cool-UI')}>
|
||||||
Cool UI Git
|
Cool UI Git
|
||||||
</Button>
|
</Button>
|
||||||
</Column>
|
</Column>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Column>
|
</Column>
|
||||||
|
|
||||||
<Column size={12}>
|
<Column size={12}>
|
||||||
<div className="credits-divider"></div>
|
<div className="credits-divider"></div>
|
||||||
</Column>
|
</Column>
|
||||||
|
|
||||||
<Column size={10}>
|
<Column size={10}>
|
||||||
<Column alignItems="center" gap={1}>
|
<Column alignItems="center" gap={1}>
|
||||||
<Text center bold fontSize={5}>Special Thanks</Text>
|
<Text center bold fontSize={5}>Special Thanks</Text>
|
||||||
@ -80,6 +78,23 @@ export const NitroSystemAlertView: FC<NotificationDefaultAlertViewProps> = props
|
|||||||
</Column>
|
</Column>
|
||||||
</Column>
|
</Column>
|
||||||
<div className="notification-frank"></div>
|
<div className="notification-frank"></div>
|
||||||
|
<Column size={12}>
|
||||||
|
<div className="credits-divider"></div>
|
||||||
|
</Column>
|
||||||
|
<Column size={10}>
|
||||||
|
<Column alignItems="center" gap={1}>
|
||||||
|
<Text center bold fontSize={5}>License</Text>
|
||||||
|
<Text center small>
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
</Text>
|
||||||
|
<Button fullWidth onClick={event => window.open('https://www.gnu.org/licenses/gpl-3.0')}>
|
||||||
|
View Full License
|
||||||
|
</Button>
|
||||||
|
</Column>
|
||||||
|
</Column>
|
||||||
</Grid>
|
</Grid>
|
||||||
</LayoutNotificationCredits>
|
</LayoutNotificationCredits>
|
||||||
);
|
);
|
||||||
|
@ -38,23 +38,24 @@ export const InfoStandWidgetFurniView: FC<InfoStandWidgetFurniViewProps> = props
|
|||||||
const [ songId, setSongId ] = useState<number>(-1);
|
const [ songId, setSongId ] = useState<number>(-1);
|
||||||
const [ songName, setSongName ] = useState<string>('');
|
const [ songName, setSongName ] = useState<string>('');
|
||||||
const [ songCreator, setSongCreator ] = useState<string>('');
|
const [ songCreator, setSongCreator ] = useState<string>('');
|
||||||
const [itemLocation, setItemLocation] = useState<{ x: number; y: number; z: number; }>({ x: -1, y: -1, z: -1 });
|
const [ itemLocation, setItemLocation ] = useState<{ x: number; y: number; z: number; }>({ x: -1, y: -1, z: -1 });
|
||||||
|
const [ furniImage, setFurniImage ] = useState<HTMLImageElement | null>(null);
|
||||||
|
|
||||||
useSoundEvent<NowPlayingEvent>(NowPlayingEvent.NPE_SONG_CHANGED, event =>
|
useSoundEvent<NowPlayingEvent>(NowPlayingEvent.NPE_SONG_CHANGED, event =>
|
||||||
{
|
{
|
||||||
setSongId(event.id);
|
setSongId(event.id);
|
||||||
}, (isJukeBox || isSongDisk));
|
}, (isJukeBox || isSongDisk));
|
||||||
|
|
||||||
useSoundEvent<NowPlayingEvent>(SongInfoReceivedEvent.SIR_TRAX_SONG_INFO_RECEIVED, event =>
|
useSoundEvent<SongInfoReceivedEvent>(SongInfoReceivedEvent.SIR_TRAX_SONG_INFO_RECEIVED, event =>
|
||||||
{
|
{
|
||||||
if(event.id !== songId) return;
|
if (event.id !== songId) return;
|
||||||
|
|
||||||
const songInfo = GetNitroInstance().soundManager.musicController.getSongInfo(event.id);
|
const songInfo = GetNitroInstance().soundManager.musicController.getSongInfo(event.id);
|
||||||
|
|
||||||
if(!songInfo) return;
|
if (!songInfo) return;
|
||||||
|
|
||||||
setSongName(songInfo.name);
|
setSongName(songInfo.name || '');
|
||||||
setSongCreator(songInfo.creator);
|
setSongCreator(songInfo.creator || '');
|
||||||
}, (isJukeBox || isSongDisk));
|
}, (isJukeBox || isSongDisk));
|
||||||
|
|
||||||
useEffect(() =>
|
useEffect(() =>
|
||||||
@ -76,32 +77,44 @@ export const InfoStandWidgetFurniView: FC<InfoStandWidgetFurniViewProps> = props
|
|||||||
let furniIsSongDisk = false;
|
let furniIsSongDisk = false;
|
||||||
let furniSongId = -1;
|
let furniSongId = -1;
|
||||||
|
|
||||||
const roomObject = GetRoomEngine().getRoomObject( roomSession.roomId, avatarInfo.id, avatarInfo.isWallItem ? RoomObjectCategory.WALL : RoomObjectCategory.FLOOR );
|
const roomObject = GetRoomEngine().getRoomObject(roomSession.roomId, avatarInfo.id, avatarInfo.isWallItem ? RoomObjectCategory.WALL : RoomObjectCategory.FLOOR);
|
||||||
const location = roomObject.getLocation();
|
const location = roomObject.getLocation();
|
||||||
if (location) {
|
if (location) {
|
||||||
setItemLocation({ x: location.x, y: location.y, z: location.z, });
|
setItemLocation({ x: location.x, y: location.y, z: location.z });
|
||||||
}
|
}
|
||||||
|
|
||||||
const isValidController = (avatarInfo.roomControllerLevel >= RoomControllerLevel.GUEST);
|
const isValidController = (avatarInfo.roomControllerLevel >= RoomControllerLevel.GUEST);
|
||||||
|
|
||||||
if(isValidController || avatarInfo.isOwner || avatarInfo.isRoomOwner || avatarInfo.isAnyRoomController)
|
if (isValidController || avatarInfo.isOwner || avatarInfo.isRoomOwner || avatarInfo.isAnyRoomController)
|
||||||
{
|
{
|
||||||
canMove = true;
|
canMove = true;
|
||||||
canRotate = !avatarInfo.isWallItem;
|
canRotate = !avatarInfo.isWallItem;
|
||||||
|
|
||||||
if(avatarInfo.roomControllerLevel >= RoomControllerLevel.MODERATOR) godMode = true;
|
if (avatarInfo.roomControllerLevel >= RoomControllerLevel.MODERATOR) godMode = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(avatarInfo.isAnyRoomController)
|
if (avatarInfo.isAnyRoomController)
|
||||||
{
|
{
|
||||||
canSeeFurniId = true;
|
canSeeFurniId = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if((((avatarInfo.usagePolicy === RoomWidgetFurniInfoUsagePolicyEnum.EVERYBODY) || ((avatarInfo.usagePolicy === RoomWidgetFurniInfoUsagePolicyEnum.CONTROLLER) && isValidController)) || ((avatarInfo.extraParam === RoomWidgetEnumItemExtradataParameter.JUKEBOX) && isValidController)) || ((avatarInfo.extraParam === RoomWidgetEnumItemExtradataParameter.USABLE_PRODUCT) && isValidController)) canUse = true;
|
try {
|
||||||
|
if (
|
||||||
|
avatarInfo.usagePolicy === RoomWidgetFurniInfoUsagePolicyEnum.EVERYBODY ||
|
||||||
|
(avatarInfo.usagePolicy === RoomWidgetFurniInfoUsagePolicyEnum.CONTROLLER && isValidController) ||
|
||||||
|
(avatarInfo.extraParam === RoomWidgetEnumItemExtradataParameter.JUKEBOX && isValidController) ||
|
||||||
|
(avatarInfo.extraParam === RoomWidgetEnumItemExtradataParameter.USABLE_PRODUCT && isValidController)
|
||||||
|
) {
|
||||||
|
canUse = true;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Error checking usage policy:', error);
|
||||||
|
}
|
||||||
|
|
||||||
if(avatarInfo.extraParam)
|
if (avatarInfo.extraParam)
|
||||||
{
|
{
|
||||||
if(avatarInfo.extraParam === RoomWidgetEnumItemExtradataParameter.CRACKABLE_FURNI)
|
try {
|
||||||
|
if (avatarInfo.extraParam === RoomWidgetEnumItemExtradataParameter.CRACKABLE_FURNI)
|
||||||
{
|
{
|
||||||
const stuffData = (avatarInfo.stuffData as CrackableDataType);
|
const stuffData = (avatarInfo.stuffData as CrackableDataType);
|
||||||
|
|
||||||
@ -110,39 +123,34 @@ export const InfoStandWidgetFurniView: FC<InfoStandWidgetFurniViewProps> = props
|
|||||||
crackableHits = stuffData.hits;
|
crackableHits = stuffData.hits;
|
||||||
crackableTarget = stuffData.target;
|
crackableTarget = stuffData.target;
|
||||||
}
|
}
|
||||||
|
else if (avatarInfo.extraParam === RoomWidgetEnumItemExtradataParameter.JUKEBOX)
|
||||||
else if(avatarInfo.extraParam === RoomWidgetEnumItemExtradataParameter.JUKEBOX)
|
|
||||||
{
|
{
|
||||||
const playlist = GetNitroInstance().soundManager.musicController.getRoomItemPlaylist();
|
const playlist = GetNitroInstance().soundManager.musicController.getRoomItemPlaylist();
|
||||||
|
if (playlist)
|
||||||
if(playlist)
|
|
||||||
{
|
{
|
||||||
furniSongId = playlist.nowPlayingSongId;
|
furniSongId = playlist.nowPlayingSongId;
|
||||||
}
|
}
|
||||||
|
|
||||||
furniIsJukebox = true;
|
furniIsJukebox = true;
|
||||||
}
|
}
|
||||||
|
else if (avatarInfo.extraParam.indexOf(RoomWidgetEnumItemExtradataParameter.SONGDISK) === 0)
|
||||||
else if(avatarInfo.extraParam.indexOf(RoomWidgetEnumItemExtradataParameter.SONGDISK) === 0)
|
|
||||||
{
|
{
|
||||||
furniSongId = parseInt(avatarInfo.extraParam.substr(RoomWidgetEnumItemExtradataParameter.SONGDISK.length));
|
furniSongId = parseInt(avatarInfo.extraParam.substr(RoomWidgetEnumItemExtradataParameter.SONGDISK.length)) || -1;
|
||||||
|
|
||||||
furniIsSongDisk = true;
|
furniIsSongDisk = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(godMode)
|
if (godMode)
|
||||||
{
|
{
|
||||||
const extraParam = avatarInfo.extraParam.substr(RoomWidgetEnumItemExtradataParameter.BRANDING_OPTIONS.length);
|
const extraParam = avatarInfo.extraParam.substr(RoomWidgetEnumItemExtradataParameter.BRANDING_OPTIONS.length);
|
||||||
|
|
||||||
if(extraParam)
|
if (extraParam)
|
||||||
{
|
{
|
||||||
const parts = extraParam.split('\t');
|
const parts = extraParam.split('\t');
|
||||||
|
|
||||||
for(const part of parts)
|
for (const part of parts)
|
||||||
{
|
{
|
||||||
const value = part.split('=');
|
const value = part.split('=');
|
||||||
|
|
||||||
if(value && (value.length === 2))
|
if (value && (value.length === 2))
|
||||||
{
|
{
|
||||||
furniKeyss.push(value[0]);
|
furniKeyss.push(value[0]);
|
||||||
furniValuess.push(value[1]);
|
furniValuess.push(value[1]);
|
||||||
@ -150,20 +158,23 @@ export const InfoStandWidgetFurniView: FC<InfoStandWidgetFurniViewProps> = props
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Error processing extraParam:', error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(godMode)
|
if (godMode)
|
||||||
{
|
{
|
||||||
const roomObject = GetRoomEngine().getRoomObject(roomSession.roomId, avatarInfo.id, (avatarInfo.isWallItem) ? RoomObjectCategory.WALL : RoomObjectCategory.FLOOR);
|
const roomObject = GetRoomEngine().getRoomObject(roomSession.roomId, avatarInfo.id, (avatarInfo.isWallItem) ? RoomObjectCategory.WALL : RoomObjectCategory.FLOOR);
|
||||||
|
|
||||||
if(roomObject)
|
if (roomObject)
|
||||||
{
|
{
|
||||||
const customVariables = roomObject.model.getValue<string[]>(RoomObjectVariable.FURNITURE_CUSTOM_VARIABLES);
|
const customVariables = roomObject.model.getValue<string[]>(RoomObjectVariable.FURNITURE_CUSTOM_VARIABLES);
|
||||||
const furnitureData = roomObject.model.getValue<{ [index: string]: string }>(RoomObjectVariable.FURNITURE_DATA);
|
const furnitureData = roomObject.model.getValue<{ [index: string]: string }>(RoomObjectVariable.FURNITURE_DATA);
|
||||||
|
|
||||||
if(customVariables && customVariables.length)
|
if (customVariables && customVariables.length)
|
||||||
{
|
{
|
||||||
for(const customVariable of customVariables)
|
for (const customVariable of customVariables)
|
||||||
{
|
{
|
||||||
customKeyss.push(customVariable);
|
customKeyss.push(customVariable);
|
||||||
customValuess.push((furnitureData[customVariable]) || '');
|
customValuess.push((furnitureData[customVariable]) || '');
|
||||||
@ -172,11 +183,10 @@ export const InfoStandWidgetFurniView: FC<InfoStandWidgetFurniViewProps> = props
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(avatarInfo.isOwner || avatarInfo.isAnyRoomController) pickupMode = PICKUP_MODE_FULL;
|
if (avatarInfo.isOwner || avatarInfo.isAnyRoomController) pickupMode = PICKUP_MODE_FULL;
|
||||||
|
else if (avatarInfo.isRoomOwner || (avatarInfo.roomControllerLevel >= RoomControllerLevel.GUILD_ADMIN)) pickupMode = PICKUP_MODE_EJECT;
|
||||||
|
|
||||||
else if(avatarInfo.isRoomOwner || (avatarInfo.roomControllerLevel >= RoomControllerLevel.GUILD_ADMIN)) pickupMode = PICKUP_MODE_EJECT;
|
if (avatarInfo.isStickie) pickupMode = PICKUP_MODE_NONE;
|
||||||
|
|
||||||
if(avatarInfo.isStickie) pickupMode = PICKUP_MODE_NONE;
|
|
||||||
|
|
||||||
setPickupMode(pickupMode);
|
setPickupMode(pickupMode);
|
||||||
setCanMove(canMove);
|
setCanMove(canMove);
|
||||||
@ -196,16 +206,26 @@ export const InfoStandWidgetFurniView: FC<InfoStandWidgetFurniViewProps> = props
|
|||||||
setIsSongDisk(furniIsSongDisk);
|
setIsSongDisk(furniIsSongDisk);
|
||||||
setSongId(furniSongId);
|
setSongId(furniSongId);
|
||||||
|
|
||||||
if(avatarInfo.groupId) SendMessageComposer(new GroupInformationComposer(avatarInfo.groupId, false));
|
if (avatarInfo.groupId) SendMessageComposer(new GroupInformationComposer(avatarInfo.groupId, false));
|
||||||
|
|
||||||
|
if (avatarInfo.image instanceof Promise) {
|
||||||
|
avatarInfo.image.then(image => {
|
||||||
|
setFurniImage(image);
|
||||||
|
}).catch(error => {
|
||||||
|
setFurniImage(null);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setFurniImage(avatarInfo.image);
|
||||||
|
}
|
||||||
}, [ roomSession, avatarInfo ]);
|
}, [ roomSession, avatarInfo ]);
|
||||||
|
|
||||||
useMessageEvent<GroupInformationEvent>(GroupInformationEvent, event =>
|
useMessageEvent<GroupInformationEvent>(GroupInformationEvent, event =>
|
||||||
{
|
{
|
||||||
const parser = event.getParser();
|
const parser = event.getParser();
|
||||||
|
|
||||||
if(!avatarInfo || avatarInfo.groupId !== parser.id || parser.flag) return;
|
if (!avatarInfo || avatarInfo.groupId !== parser.id || parser.flag) return;
|
||||||
|
|
||||||
if(groupName) setGroupName(null);
|
if (groupName) setGroupName(null);
|
||||||
|
|
||||||
setGroupName(parser.title);
|
setGroupName(parser.title);
|
||||||
});
|
});
|
||||||
@ -213,44 +233,36 @@ export const InfoStandWidgetFurniView: FC<InfoStandWidgetFurniViewProps> = props
|
|||||||
useEffect(() =>
|
useEffect(() =>
|
||||||
{
|
{
|
||||||
const songInfo = GetNitroInstance().soundManager.musicController.getSongInfo(songId);
|
const songInfo = GetNitroInstance().soundManager.musicController.getSongInfo(songId);
|
||||||
|
setSongName(songInfo && songInfo.name ? songInfo.name : '');
|
||||||
setSongName(songInfo?.name ?? '');
|
setSongCreator(songInfo && songInfo.creator ? songInfo.creator : '');
|
||||||
setSongCreator(songInfo?.creator ?? '');
|
|
||||||
}, [ songId ]);
|
}, [ songId ]);
|
||||||
|
|
||||||
const onFurniSettingChange = useCallback((index: number, value: string) =>
|
const onFurniSettingChange = useCallback((index: number, value: string) =>
|
||||||
{
|
{
|
||||||
const clone = Array.from(furniValues);
|
const clone = Array.from(furniValues);
|
||||||
|
|
||||||
clone[index] = value;
|
clone[index] = value;
|
||||||
|
|
||||||
setFurniValues(clone);
|
setFurniValues(clone);
|
||||||
}, [ furniValues ]);
|
}, [ furniValues ]);
|
||||||
|
|
||||||
const onCustomVariableChange = useCallback((index: number, value: string) =>
|
const onCustomVariableChange = useCallback((index: number, value: string) =>
|
||||||
{
|
{
|
||||||
const clone = Array.from(customValues);
|
const clone = Array.from(customValues);
|
||||||
|
|
||||||
clone[index] = value;
|
clone[index] = value;
|
||||||
|
|
||||||
setCustomValues(clone);
|
setCustomValues(clone);
|
||||||
}, [ customValues ]);
|
}, [ customValues ]);
|
||||||
|
|
||||||
const getFurniSettingsAsString = useCallback(() =>
|
const getFurniSettingsAsString = useCallback(() =>
|
||||||
{
|
{
|
||||||
if(furniKeys.length === 0 || furniValues.length === 0) return '';
|
if (furniKeys.length === 0 || furniValues.length === 0) return '';
|
||||||
|
|
||||||
let data = '';
|
let data = '';
|
||||||
|
|
||||||
let i = 0;
|
let i = 0;
|
||||||
|
|
||||||
while(i < furniKeys.length)
|
while (i < furniKeys.length)
|
||||||
{
|
{
|
||||||
const key = furniKeys[i];
|
const key = furniKeys[i];
|
||||||
const value = furniValues[i];
|
const value = furniValues[i];
|
||||||
|
|
||||||
data = (data + (key + '=' + value + '\t'));
|
data = (data + (key + '=' + value + '\t'));
|
||||||
|
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -259,11 +271,11 @@ export const InfoStandWidgetFurniView: FC<InfoStandWidgetFurniViewProps> = props
|
|||||||
|
|
||||||
const processButtonAction = useCallback((action: string) =>
|
const processButtonAction = useCallback((action: string) =>
|
||||||
{
|
{
|
||||||
if(!action || (action === '')) return;
|
if (!action || (action === '')) return;
|
||||||
|
|
||||||
let objectData: string = null;
|
let objectData: string = null;
|
||||||
|
|
||||||
switch(action)
|
switch (action)
|
||||||
{
|
{
|
||||||
case 'buy_one':
|
case 'buy_one':
|
||||||
CreateLinkEvent(`catalog/open/offerId/${ avatarInfo.purchaseOfferId }`);
|
CreateLinkEvent(`catalog/open/offerId/${ avatarInfo.purchaseOfferId }`);
|
||||||
@ -275,7 +287,7 @@ export const InfoStandWidgetFurniView: FC<InfoStandWidgetFurniViewProps> = props
|
|||||||
GetRoomEngine().processRoomObjectOperation(avatarInfo.id, avatarInfo.category, RoomObjectOperationType.OBJECT_ROTATE_POSITIVE);
|
GetRoomEngine().processRoomObjectOperation(avatarInfo.id, avatarInfo.category, RoomObjectOperationType.OBJECT_ROTATE_POSITIVE);
|
||||||
break;
|
break;
|
||||||
case 'pickup':
|
case 'pickup':
|
||||||
if(pickupMode === PICKUP_MODE_FULL)
|
if (pickupMode === PICKUP_MODE_FULL)
|
||||||
{
|
{
|
||||||
GetRoomEngine().processRoomObjectOperation(avatarInfo.id, avatarInfo.category, RoomObjectOperationType.OBJECT_PICKUP);
|
GetRoomEngine().processRoomObjectOperation(avatarInfo.id, avatarInfo.category, RoomObjectOperationType.OBJECT_PICKUP);
|
||||||
}
|
}
|
||||||
@ -291,12 +303,11 @@ export const InfoStandWidgetFurniView: FC<InfoStandWidgetFurniViewProps> = props
|
|||||||
const mapData = new Map<string, string>();
|
const mapData = new Map<string, string>();
|
||||||
const dataParts = getFurniSettingsAsString().split('\t');
|
const dataParts = getFurniSettingsAsString().split('\t');
|
||||||
|
|
||||||
if(dataParts)
|
if (dataParts)
|
||||||
{
|
{
|
||||||
for(const part of dataParts)
|
for (const part of dataParts)
|
||||||
{
|
{
|
||||||
const [ key, value ] = part.split('=', 2);
|
const [ key, value ] = part.split('=', 2);
|
||||||
|
|
||||||
mapData.set(key, value);
|
mapData.set(key, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -307,12 +318,12 @@ export const InfoStandWidgetFurniView: FC<InfoStandWidgetFurniViewProps> = props
|
|||||||
case 'save_custom_variables': {
|
case 'save_custom_variables': {
|
||||||
const map = new Map();
|
const map = new Map();
|
||||||
|
|
||||||
for(let i = 0; i < customKeys.length; i++)
|
for (let i = 0; i < customKeys.length; i++)
|
||||||
{
|
{
|
||||||
const key = customKeys[i];
|
const key = customKeys[i];
|
||||||
const value = customValues[i];
|
const value = customValues[i];
|
||||||
|
|
||||||
if((key && key.length) && (value && value.length)) map.set(key, value);
|
if ((key && key.length) && (value && value.length)) map.set(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
SendMessageComposer(new SetObjectDataMessageComposer(avatarInfo.id, map));
|
SendMessageComposer(new SetObjectDataMessageComposer(avatarInfo.id, map));
|
||||||
@ -325,12 +336,12 @@ export const InfoStandWidgetFurniView: FC<InfoStandWidgetFurniViewProps> = props
|
|||||||
{
|
{
|
||||||
const stringDataType = (avatarInfo.stuffData as StringDataType);
|
const stringDataType = (avatarInfo.stuffData as StringDataType);
|
||||||
|
|
||||||
if(!stringDataType || !(stringDataType instanceof StringDataType)) return null;
|
if (!stringDataType || !(stringDataType instanceof StringDataType)) return null;
|
||||||
|
|
||||||
return stringDataType.getValue(2);
|
return stringDataType.getValue(2);
|
||||||
}, [ avatarInfo ]);
|
}, [ avatarInfo ]);
|
||||||
|
|
||||||
if(!avatarInfo) return null;
|
if (!avatarInfo) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column gap={ 1 } alignItems="end">
|
<Column gap={ 1 } alignItems="end">
|
||||||
@ -339,7 +350,7 @@ export const InfoStandWidgetFurniView: FC<InfoStandWidgetFurniViewProps> = props
|
|||||||
<Column gap={ 1 }>
|
<Column gap={ 1 }>
|
||||||
<Flex alignItems="center" justifyContent="between" gap={ 1 }>
|
<Flex alignItems="center" justifyContent="between" gap={ 1 }>
|
||||||
{ !(isSongDisk) && <Text variant="white" wrap>{ avatarInfo.name }</Text> }
|
{ !(isSongDisk) && <Text variant="white" wrap>{ avatarInfo.name }</Text> }
|
||||||
{ (songName.length > 0) && <Text variant="white" wrap>{ songName }</Text> }
|
{ songName && songName.length > 0 && <Text variant="white" wrap>{ songName }</Text> }
|
||||||
<i className="infostand-close" onClick={ onClose } />
|
<i className="infostand-close" onClick={ onClose } />
|
||||||
</Flex>
|
</Flex>
|
||||||
<hr className="m-0" />
|
<hr className="m-0" />
|
||||||
@ -354,8 +365,12 @@ export const InfoStandWidgetFurniView: FC<InfoStandWidgetFurniViewProps> = props
|
|||||||
<div className="position-absolute end-0">
|
<div className="position-absolute end-0">
|
||||||
<LayoutRarityLevelView level={ avatarInfo.stuffData.rarityLevel } />
|
<LayoutRarityLevelView level={ avatarInfo.stuffData.rarityLevel } />
|
||||||
</div> }
|
</div> }
|
||||||
{ avatarInfo.image && avatarInfo.image.src.length &&
|
{ furniImage && furniImage.src && typeof furniImage.src === 'string' && furniImage.src.length > 0 ?
|
||||||
<img className="d-block mx-auto" src={ avatarInfo.image.src } alt="" /> }
|
<>
|
||||||
|
<img className="d-block mx-auto" src={ furniImage.src } alt="" />
|
||||||
|
</> :
|
||||||
|
console.log('Skipping furni image: Invalid or missing src', furniImage)
|
||||||
|
}
|
||||||
</Flex>
|
</Flex>
|
||||||
<hr className="m-0" />
|
<hr className="m-0" />
|
||||||
</Column>
|
</Column>
|
||||||
@ -384,14 +399,14 @@ export const InfoStandWidgetFurniView: FC<InfoStandWidgetFurniViewProps> = props
|
|||||||
<Text variant="white" small wrap>
|
<Text variant="white" small wrap>
|
||||||
{ LocalizeText('infostand.jukebox.text.not.playing') }
|
{ LocalizeText('infostand.jukebox.text.not.playing') }
|
||||||
</Text> }
|
</Text> }
|
||||||
{ !!songName.length &&
|
{ songName && songName.length > 0 &&
|
||||||
<Flex alignItems="center" gap={ 1 }>
|
<Flex alignItems="center" gap={ 1 }>
|
||||||
<Base className="icon disk-icon" />
|
<Base className="icon disk-icon" />
|
||||||
<Text variant="white" small wrap>
|
<Text variant="white" small wrap>
|
||||||
{ songName }
|
{ songName }
|
||||||
</Text>
|
</Text>
|
||||||
</Flex> }
|
</Flex> }
|
||||||
{ !!songCreator.length &&
|
{ songCreator && songCreator.length > 0 &&
|
||||||
<Flex alignItems="center" gap={ 1 }>
|
<Flex alignItems="center" gap={ 1 }>
|
||||||
<Base className="icon disk-creator" />
|
<Base className="icon disk-creator" />
|
||||||
<Text variant="white" small wrap>
|
<Text variant="white" small wrap>
|
||||||
|
@ -157,7 +157,7 @@ export const InfoStandWidgetUserView: FC<InfoStandWidgetUserViewProps> = props =
|
|||||||
<Flex gap={ 1 }>
|
<Flex gap={ 1 }>
|
||||||
<Column position="relative" pointer fullWidth className={ `body-image profile-background ${ infostandBackgroundClass }` } onClick={ event => GetUserProfile(avatarInfo.webID) }>
|
<Column position="relative" pointer fullWidth className={ `body-image profile-background ${ infostandBackgroundClass }` } onClick={ event => GetUserProfile(avatarInfo.webID) }>
|
||||||
<Base position="absolute" className={ `body-image profile-stand ${ infostandStandClass }` }/>
|
<Base position="absolute" className={ `body-image profile-stand ${ infostandStandClass }` }/>
|
||||||
<LayoutAvatarImageView figure={ avatarInfo.figure } direction={ 4 } />
|
<LayoutAvatarImageView figure={ avatarInfo.figure } direction={ 2 } style={{ position: 'relative', top: '-10px' }} />
|
||||||
<Base position="absolute" className={ `body-image profile-overlay ${ infostandOverlayClass }` }/>
|
<Base position="absolute" className={ `body-image profile-overlay ${ infostandOverlayClass }` }/>
|
||||||
{ avatarInfo.type === AvatarInfoUser.OWN_USER &&
|
{ avatarInfo.type === AvatarInfoUser.OWN_USER &&
|
||||||
<Base position="absolute" className="icon edit-icon edit-icon-position" onClick={ event =>
|
<Base position="absolute" className="icon edit-icon edit-icon-position" onClick={ event =>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { MouseEventType, RoomObjectCategory } from '@nitrots/nitro-renderer';
|
import { MouseEventType, RoomObjectCategory } from '@nitrots/nitro-renderer';
|
||||||
import { Dispatch, FC, PropsWithChildren, SetStateAction, useEffect, useRef } from 'react';
|
import { Dispatch, FC, PropsWithChildren, SetStateAction, useEffect, useRef } from 'react';
|
||||||
import { CreateLinkEvent, DispatchUiEvent, GetConfiguration, GetRoomEngine, GetRoomSession, GetUserProfile } from '../../api';
|
import { CreateLinkEvent, DispatchUiEvent, GetConfiguration, GetRoomEngine, GetRoomSession, GetSessionDataManager, GetUserProfile } from '../../api';
|
||||||
import { Base, Flex, LayoutItemCountView } from '../../common';
|
import { Base, Flex, LayoutItemCountView } from '../../common';
|
||||||
import { GuideToolEvent } from '../../events';
|
import { GuideToolEvent } from '../../events';
|
||||||
|
|
||||||
|
@ -22,38 +22,39 @@
|
|||||||
},
|
},
|
||||||
"main": "./index",
|
"main": "./index",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@pixi/app": "~6.5.10",
|
"@pixi/app": "~7.4.3",
|
||||||
"@pixi/basis": "~6.5.10",
|
"@pixi/assets": "~7.4.3",
|
||||||
"@pixi/canvas-display": "~6.5.10",
|
"@pixi/basis": "~7.4.3",
|
||||||
"@pixi/canvas-extract": "~6.5.10",
|
"@pixi/canvas-display": "~7.4.3",
|
||||||
"@pixi/canvas-renderer": "~6.5.10",
|
"@pixi/canvas-extract": "~7.4.3",
|
||||||
"@pixi/constants": "~6.5.10",
|
"@pixi/canvas-renderer": "~7.4.3",
|
||||||
"@pixi/core": "~6.5.10",
|
"@pixi/constants": "~7.4.3",
|
||||||
"@pixi/display": "~6.5.10",
|
"@pixi/core": "~7.4.3",
|
||||||
"@pixi/events": "~6.5.10",
|
"@pixi/display": "~7.4.3",
|
||||||
"@pixi/extensions": "~6.5.10",
|
"@pixi/events": "~7.4.3",
|
||||||
"@pixi/extract": "~6.5.10",
|
"@pixi/extensions": "~7.4.3",
|
||||||
"@pixi/filter-alpha": "~6.5.10",
|
"@pixi/extract": "~7.4.3",
|
||||||
"@pixi/filter-color-matrix": "~6.5.10",
|
"@pixi/filter-alpha": "~7.4.3",
|
||||||
"@pixi/graphics": "~6.5.10",
|
"@pixi/filter-color-matrix": "~7.4.3",
|
||||||
"@pixi/graphics-extras": "~6.5.10",
|
"@pixi/graphics": "~7.4.3",
|
||||||
|
"@pixi/graphics-extras": "~7.4.3",
|
||||||
"@pixi/interaction": "~6.5.10",
|
"@pixi/interaction": "~6.5.10",
|
||||||
"@pixi/loaders": "~6.5.10",
|
"@pixi/loaders": "~6.5.10",
|
||||||
"@pixi/math": "~6.5.10",
|
"@pixi/math": "~7.4.3",
|
||||||
"@pixi/math-extras": "~6.5.10",
|
"@pixi/math-extras": "~7.4.3",
|
||||||
"@pixi/mixin-cache-as-bitmap": "~6.5.10",
|
"@pixi/mixin-cache-as-bitmap": "~7.4.3",
|
||||||
"@pixi/mixin-get-child-by-name": "~6.5.10",
|
"@pixi/mixin-get-child-by-name": "~7.4.3",
|
||||||
"@pixi/mixin-get-global-position": "~6.5.10",
|
"@pixi/mixin-get-global-position": "~7.4.3",
|
||||||
"@pixi/polyfill": "~6.5.10",
|
"@pixi/polyfill": "~6.5.10",
|
||||||
"@pixi/runner": "~6.5.10",
|
"@pixi/runner": "~7.4.3",
|
||||||
"@pixi/settings": "~6.5.10",
|
"@pixi/settings": "~7.4.3",
|
||||||
"@pixi/sprite": "~6.5.10",
|
"@pixi/sprite": "~7.4.3",
|
||||||
"@pixi/sprite-tiling": "~6.5.10",
|
"@pixi/sprite-tiling": "~7.4.3",
|
||||||
"@pixi/spritesheet": "~6.5.10",
|
"@pixi/spritesheet": "~7.4.3",
|
||||||
"@pixi/text": "~6.5.10",
|
"@pixi/text": "~7.4.3",
|
||||||
"@pixi/ticker": "~6.5.10",
|
"@pixi/ticker": "~7.4.3",
|
||||||
"@pixi/tilemap": "^3.2.2",
|
"@pixi/tilemap": "^5.0.1",
|
||||||
"@pixi/utils": "~6.5.10",
|
"@pixi/utils": "~7.4.3",
|
||||||
"clientjs": "^0.2.1",
|
"clientjs": "^0.2.1",
|
||||||
"gifuct-js": "^2.1.2",
|
"gifuct-js": "^2.1.2",
|
||||||
"howler": "^2.2.3",
|
"howler": "^2.2.3",
|
||||||
@ -68,7 +69,7 @@
|
|||||||
"@typescript-eslint/parser": "^5.30.7",
|
"@typescript-eslint/parser": "^5.30.7",
|
||||||
"eslint": "^8.20.0",
|
"eslint": "^8.20.0",
|
||||||
"tslib": "^2.3.1",
|
"tslib": "^2.3.1",
|
||||||
"typescript": "~4.4.4",
|
"typescript": "~5.4.5",
|
||||||
"vite": "^4.0.2",
|
"vite": "^4.0.2",
|
||||||
"vite-plugin-minify": "^1.5.2"
|
"vite-plugin-minify": "^1.5.2"
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ export interface IAvatarImage extends IDisposable
|
|||||||
getLayerData(_arg_1: ISpriteDataContainer): IAnimationLayerData;
|
getLayerData(_arg_1: ISpriteDataContainer): IAnimationLayerData;
|
||||||
getImage(setType: string, hightlight: boolean, scale?: number, cache?: boolean): RenderTexture;
|
getImage(setType: string, hightlight: boolean, scale?: number, cache?: boolean): RenderTexture;
|
||||||
getImageAsSprite(setType: string, scale?: number): Sprite;
|
getImageAsSprite(setType: string, scale?: number): Sprite;
|
||||||
getCroppedImage(setType: string, scale?: number): HTMLImageElement;
|
getCroppedImage(setType: string, scale?: number): Promise<HTMLImageElement>; // Ensure async
|
||||||
getAsset(_arg_1: string): IGraphicAsset;
|
getAsset(_arg_1: string): IGraphicAsset;
|
||||||
getDirection(): number;
|
getDirection(): number;
|
||||||
getFigure(): IAvatarFigureContainer;
|
getFigure(): IAvatarFigureContainer;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
export class NitroVersion
|
export class NitroVersion
|
||||||
{
|
{
|
||||||
public static RENDERER_VERSION: string = '1.6.6';
|
public static RENDERER_VERSION: string = '1.7.4';
|
||||||
public static UI_VERSION: string = '';
|
public static UI_VERSION: string = '';
|
||||||
|
|
||||||
public static sayHello(): void
|
public static sayHello(): void
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import '@pixi/canvas-display';
|
import '@pixi/canvas-display';
|
||||||
import { BatchRenderer, extensions } from '@pixi/core';
|
import { extensions } from '@pixi/core';
|
||||||
import { Extract } from '@pixi/extract';
|
|
||||||
import '@pixi/graphics-extras';
|
import '@pixi/graphics-extras';
|
||||||
import { AppLoaderPlugin } from '@pixi/loaders';
|
import { AppLoaderPlugin } from '@pixi/loaders';
|
||||||
import '@pixi/math-extras';
|
import '@pixi/math-extras';
|
||||||
@ -9,13 +8,8 @@ import '@pixi/mixin-get-child-by-name';
|
|||||||
import '@pixi/mixin-get-global-position';
|
import '@pixi/mixin-get-global-position';
|
||||||
import '@pixi/polyfill';
|
import '@pixi/polyfill';
|
||||||
import { TilingSpriteRenderer } from '@pixi/sprite-tiling';
|
import { TilingSpriteRenderer } from '@pixi/sprite-tiling';
|
||||||
import { SpritesheetLoader } from '@pixi/spritesheet';
|
|
||||||
import { TickerPlugin } from '@pixi/ticker';
|
import { TickerPlugin } from '@pixi/ticker';
|
||||||
|
|
||||||
extensions.add(
|
extensions.add(
|
||||||
BatchRenderer,
|
TilingSpriteRenderer
|
||||||
Extract,
|
);
|
||||||
TilingSpriteRenderer,
|
|
||||||
SpritesheetLoader,
|
|
||||||
AppLoaderPlugin,
|
|
||||||
TickerPlugin);
|
|
@ -504,58 +504,60 @@ export class AvatarImage implements IAvatarImage, IAvatarEffectListener
|
|||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getCroppedImage(setType: string, scale: number = 1): HTMLImageElement
|
public async getCroppedImage(setType: string, scale: number = 1): Promise<HTMLImageElement>
|
||||||
{
|
{
|
||||||
if(!this._mainAction) return null;
|
if (!this._mainAction) {
|
||||||
|
console.warn('getCroppedImage: No main action');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if(!this._actionsSorted) this.endActionAppends();
|
if (!this._actionsSorted) this.endActionAppends();
|
||||||
|
|
||||||
const avatarCanvas = this._structure.getCanvas(this._scale, this._mainAction.definition.geometryType);
|
const avatarCanvas = this._structure.getCanvas(this._scale, this._mainAction.definition.geometryType);
|
||||||
|
|
||||||
if(!avatarCanvas) return null;
|
if (!avatarCanvas) {
|
||||||
|
console.warn('getCroppedImage: No avatar canvas');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const setTypes = this.getBodyParts(setType, this._mainAction.definition.geometryType, this._mainDirection);
|
const setTypes = this.getBodyParts(setType, this._mainAction.definition.geometryType, this._mainDirection);
|
||||||
const container = new NitroContainer();
|
const container = new NitroContainer();
|
||||||
|
|
||||||
let partCount = (setTypes.length - 1);
|
let partCount = setTypes.length - 1;
|
||||||
|
let partsAdded = 0;
|
||||||
|
|
||||||
while(partCount >= 0)
|
while (partCount >= 0)
|
||||||
{
|
{
|
||||||
const set = setTypes[partCount];
|
const set = setTypes[partCount];
|
||||||
const part = this._cache.getImageContainer(set, this._frameCounter);
|
const part = this._cache.getImageContainer(set, this._frameCounter);
|
||||||
|
|
||||||
if(part)
|
if (part)
|
||||||
{
|
{
|
||||||
const partCacheContainer = part.image;
|
const partCacheContainer = part.image;
|
||||||
|
|
||||||
if(!partCacheContainer)
|
if (!partCacheContainer) {
|
||||||
{
|
console.warn(`getCroppedImage: No image for part ${set}`);
|
||||||
container.destroy({
|
container.destroy({ children: true });
|
||||||
children: true
|
|
||||||
});
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const point = part.regPoint.clone();
|
const point = part.regPoint.clone();
|
||||||
|
|
||||||
if(point)
|
if (point)
|
||||||
{
|
{
|
||||||
point.x += avatarCanvas.offset.x;
|
point.x += avatarCanvas.offset.x;
|
||||||
point.y += avatarCanvas.offset.y;
|
point.y += avatarCanvas.offset.y;
|
||||||
|
|
||||||
point.x += avatarCanvas.regPoint.x;
|
point.x += avatarCanvas.regPoint.x;
|
||||||
point.y += avatarCanvas.regPoint.y;
|
point.y += avatarCanvas.regPoint.y;
|
||||||
|
|
||||||
const partContainer = new NitroContainer();
|
const partContainer = new NitroContainer();
|
||||||
|
|
||||||
partContainer.addChild(partCacheContainer);
|
partContainer.addChild(partCacheContainer);
|
||||||
|
|
||||||
if(partContainer)
|
if (partContainer)
|
||||||
{
|
{
|
||||||
partContainer.position.set(point.x, point.y);
|
partContainer.position.set(point.x, point.y);
|
||||||
|
|
||||||
container.addChild(partContainer);
|
container.addChild(partContainer);
|
||||||
|
partsAdded++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -565,9 +567,22 @@ export class AvatarImage implements IAvatarImage, IAvatarEffectListener
|
|||||||
|
|
||||||
const texture = TextureUtils.generateTexture(container, new Rectangle(0, 0, avatarCanvas.width, avatarCanvas.height));
|
const texture = TextureUtils.generateTexture(container, new Rectangle(0, 0, avatarCanvas.width, avatarCanvas.height));
|
||||||
|
|
||||||
const image = TextureUtils.generateImage(texture);
|
if (!texture) {
|
||||||
|
console.warn('getCroppedImage: Failed to generate texture');
|
||||||
|
container.destroy({ children: true });
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if(!image) return null;
|
const image = await TextureUtils.generateImage(texture);
|
||||||
|
|
||||||
|
container.destroy({ children: true });
|
||||||
|
|
||||||
|
if (!image || !image.src) {
|
||||||
|
console.warn('getCroppedImage: Invalid image generated', image);
|
||||||
|
const fallback = new Image();
|
||||||
|
fallback.src = '';
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,11 @@ export class RenderRoomMessageComposer implements IMessageComposer<ConstructorPa
|
|||||||
{
|
{
|
||||||
const url = TextureUtils.generateImageUrl(texture);
|
const url = TextureUtils.generateImageUrl(texture);
|
||||||
|
|
||||||
if(!url) return;
|
if(!url || typeof url !== 'string' || !url.startsWith('data:image/'))
|
||||||
|
{
|
||||||
|
console.warn('RenderRoomMessageComposer: Invalid or missing image URL', { url });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const base64Data = url.split(',')[1];
|
const base64Data = url.split(',')[1];
|
||||||
const binaryData = Uint8Array.from(atob(base64Data), c => c.charCodeAt(0));
|
const binaryData = Uint8Array.from(atob(base64Data), c => c.charCodeAt(0));
|
||||||
@ -35,6 +39,12 @@ export class RenderRoomMessageComposer implements IMessageComposer<ConstructorPa
|
|||||||
|
|
||||||
public assignBase64(base64: string): void
|
public assignBase64(base64: string): void
|
||||||
{
|
{
|
||||||
|
if(!base64 || typeof base64 !== 'string' || !base64.startsWith('data:image/'))
|
||||||
|
{
|
||||||
|
console.warn('RenderRoomMessageComposer: Invalid or missing base64 data', { base64 });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const base64Data = base64.split(',')[1];
|
const base64Data = base64.split(',')[1];
|
||||||
const binaryData = Uint8Array.from(atob(base64Data), c => c.charCodeAt(0));
|
const binaryData = Uint8Array.from(atob(base64Data), c => c.charCodeAt(0));
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ export class FurnitureChangeStateWhenStepOnLogic extends FurnitureLogic
|
|||||||
let sizeX = this.object.model.getValue<number>(RoomObjectVariable.FURNITURE_SIZE_X);
|
let sizeX = this.object.model.getValue<number>(RoomObjectVariable.FURNITURE_SIZE_X);
|
||||||
let sizeY = this.object.model.getValue<number>(RoomObjectVariable.FURNITURE_SIZE_Y);
|
let sizeY = this.object.model.getValue<number>(RoomObjectVariable.FURNITURE_SIZE_Y);
|
||||||
|
|
||||||
const direction = (((Math.floor(this.object.getDirection().x) + 45) % 360) / 90);
|
const direction = Math.floor(((this.object.getDirection().x + 45) % 360) / 90);
|
||||||
|
|
||||||
if((direction === 1) || (direction === 3)) [sizeX, sizeY] = [sizeY, sizeX];
|
if((direction === 1) || (direction === 3)) [sizeX, sizeY] = [sizeY, sizeX];
|
||||||
|
|
||||||
|
@ -14,34 +14,48 @@ export class FurnitureDynamicThumbnailVisualization extends IsometricImageFurniV
|
|||||||
this._hasOutline = true;
|
this._hasOutline = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected updateModel(scale: number): boolean {
|
protected async updateModel(scale: number): Promise<boolean>
|
||||||
if (this.object) {
|
{
|
||||||
|
if (this.object)
|
||||||
|
{
|
||||||
const thumbnailUrl = this.getThumbnailURL();
|
const thumbnailUrl = this.getThumbnailURL();
|
||||||
|
|
||||||
if (this._cachedUrl !== thumbnailUrl) {
|
if (this._cachedUrl !== thumbnailUrl)
|
||||||
|
{
|
||||||
this._cachedUrl = thumbnailUrl;
|
this._cachedUrl = thumbnailUrl;
|
||||||
|
|
||||||
if (this._cachedUrl && this._cachedUrl !== '') {
|
if (this._cachedUrl && this._cachedUrl !== '')
|
||||||
|
{
|
||||||
const image = new Image();
|
const image = new Image();
|
||||||
|
|
||||||
image.src = thumbnailUrl;
|
image.src = thumbnailUrl;
|
||||||
image.crossOrigin = '*';
|
image.crossOrigin = '*';
|
||||||
|
|
||||||
|
await new Promise<void>((resolve) => {
|
||||||
image.onload = () => {
|
image.onload = () => {
|
||||||
const texture = Texture.from(image);
|
const texture = Texture.from(image);
|
||||||
|
|
||||||
texture.baseTexture.scaleMode = SCALE_MODES.LINEAR;
|
texture.baseTexture.scaleMode = SCALE_MODES.LINEAR;
|
||||||
|
|
||||||
this.setThumbnailImages(texture);
|
this.setThumbnailImages(texture);
|
||||||
|
resolve();
|
||||||
};
|
};
|
||||||
} else {
|
image.onerror = () => {
|
||||||
|
console.warn('FurnitureDynamicThumbnailVisualization: Failed to load thumbnail image', { thumbnailUrl });
|
||||||
|
this.setThumbnailImages(null);
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
this.setThumbnailImages(null);
|
this.setThumbnailImages(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.updateModel(scale);
|
return await super.updateModel(scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getThumbnailURL(): string
|
protected getThumbnailURL(): string
|
||||||
{
|
{
|
||||||
|
@ -12,6 +12,8 @@ export class IsometricImageFurniVisualization extends FurnitureAnimatedVisualiza
|
|||||||
private _thumbnailImageNormal: Texture<Resource>;
|
private _thumbnailImageNormal: Texture<Resource>;
|
||||||
private _thumbnailDirection: number;
|
private _thumbnailDirection: number;
|
||||||
private _thumbnailChanged: boolean;
|
private _thumbnailChanged: boolean;
|
||||||
|
private _uniqueId: string;
|
||||||
|
private _photoUrl: string;
|
||||||
protected _hasOutline: boolean;
|
protected _hasOutline: boolean;
|
||||||
|
|
||||||
constructor()
|
constructor()
|
||||||
@ -22,7 +24,9 @@ export class IsometricImageFurniVisualization extends FurnitureAnimatedVisualiza
|
|||||||
this._thumbnailImageNormal = null;
|
this._thumbnailImageNormal = null;
|
||||||
this._thumbnailDirection = -1;
|
this._thumbnailDirection = -1;
|
||||||
this._thumbnailChanged = false;
|
this._thumbnailChanged = false;
|
||||||
this._hasOutline = false;
|
this._uniqueId = `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
|
||||||
|
this._photoUrl = null;
|
||||||
|
this._hasOutline = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get hasThumbnailImage(): boolean
|
public get hasThumbnailImage(): boolean
|
||||||
@ -30,58 +34,83 @@ export class IsometricImageFurniVisualization extends FurnitureAnimatedVisualiza
|
|||||||
return !(this._thumbnailImageNormal == null);
|
return !(this._thumbnailImageNormal == null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public setThumbnailImages(texture: Texture<Resource>): void
|
public setThumbnailImages(k: Texture<Resource>, url?: string): void
|
||||||
{
|
{
|
||||||
this._thumbnailImageNormal = texture;
|
this._thumbnailImageNormal = k;
|
||||||
|
this._photoUrl = url || null;
|
||||||
this._thumbnailChanged = true;
|
this._thumbnailChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected updateModel(scale: number): boolean
|
public getPhotoUrl(): string
|
||||||
{
|
{
|
||||||
const flag = super.updateModel(scale);
|
return this._photoUrl;
|
||||||
|
}
|
||||||
|
|
||||||
if(!this._thumbnailChanged && (this._thumbnailDirection === this.direction)) return flag;
|
protected async updateModel(scale: number): Promise<boolean>
|
||||||
|
{
|
||||||
|
const flag = await super.updateModel(scale);
|
||||||
|
|
||||||
this.refreshThumbnail();
|
if (!this._thumbnailChanged && (this._thumbnailDirection === this.direction)) {
|
||||||
|
return flag;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.refreshThumbnail();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private refreshThumbnail(): void
|
private async refreshThumbnail(): Promise<void>
|
||||||
{
|
{
|
||||||
if(this.asset == null) return;
|
if (this.asset == null) {
|
||||||
|
return;
|
||||||
if(this._thumbnailImageNormal)
|
|
||||||
{
|
|
||||||
this.addThumbnailAsset(this._thumbnailImageNormal, 64);
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
const thumbnailAssetName = this.getThumbnailAssetName(64);
|
||||||
this.asset.disposeAsset(this.getThumbnailAssetName(64));
|
|
||||||
|
if (this._thumbnailImageNormal) {
|
||||||
|
await this.addThumbnailAsset(this._thumbnailImageNormal, 64);
|
||||||
|
} else {
|
||||||
|
const layerId = 2;
|
||||||
|
const sprite = this.getSprite(layerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._thumbnailChanged = false;
|
this._thumbnailChanged = false;
|
||||||
this._thumbnailDirection = this.direction;
|
this._thumbnailDirection = this.direction;
|
||||||
}
|
}
|
||||||
|
|
||||||
private addThumbnailAsset(texture: Texture<Resource>, scale: number): void
|
private async addThumbnailAsset(k: Texture<Resource>, scale: number): Promise<void>
|
||||||
{
|
{
|
||||||
let layerId = 0;
|
let layerId = 0;
|
||||||
|
|
||||||
while(layerId < this.totalSprites)
|
while (layerId < this.totalSprites)
|
||||||
{
|
{
|
||||||
if(this.getLayerTag(scale, this.direction, layerId) === IsometricImageFurniVisualization.THUMBNAIL)
|
const layerTag = this.getLayerTag(scale, this.direction, layerId);
|
||||||
|
|
||||||
|
if (layerTag === IsometricImageFurniVisualization.THUMBNAIL)
|
||||||
{
|
{
|
||||||
const assetName = (this.cacheSpriteAssetName(scale, layerId, false) + this.getFrameNumber(scale, layerId));
|
const assetName = (this.cacheSpriteAssetName(scale, layerId, false) + this.getFrameNumber(scale, layerId));
|
||||||
const asset = this.getAsset(assetName, layerId);
|
const asset = this.getAsset(assetName, layerId);
|
||||||
|
const thumbnailAssetName = `${this.getThumbnailAssetName(scale)}-${this._uniqueId}`;
|
||||||
|
const transformedTexture = await this.generateTransformedThumbnail(k, asset || { width: 64, height: 64, offsetX: 0, offsetY: 0 });
|
||||||
|
|
||||||
if(asset)
|
if (!transformedTexture) {
|
||||||
{
|
console.warn('IsometricImageFurniVisualization: Failed to generate transformed thumbnail for asset', { assetName });
|
||||||
const _local_6 = this.generateTransformedThumbnail(texture, asset);
|
return;
|
||||||
const _local_7 = this.getThumbnailAssetName(scale);
|
}
|
||||||
|
|
||||||
this.asset.disposeAsset(_local_7);
|
const baseOffsetX = asset?.offsetX || 0;
|
||||||
this.asset.addAsset(_local_7, _local_6, true, asset.offsetX, asset.offsetY, false, false);
|
const baseOffsetY = asset?.offsetY || 0;
|
||||||
|
|
||||||
|
const offsetX = baseOffsetX - (transformedTexture.width / 2);
|
||||||
|
const offsetY = baseOffsetY - (transformedTexture.height / 2);
|
||||||
|
|
||||||
|
this.asset.addAsset(thumbnailAssetName, transformedTexture, true, offsetX, offsetY, false, false);
|
||||||
|
|
||||||
|
const sprite = this.getSprite(layerId);
|
||||||
|
if (sprite) {
|
||||||
|
sprite.texture = transformedTexture;
|
||||||
|
} else {
|
||||||
|
console.warn('IsometricImageFurniVisualization: Sprite not found for layer', { layerId });
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@ -91,67 +120,75 @@ export class IsometricImageFurniVisualization extends FurnitureAnimatedVisualiza
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected generateTransformedThumbnail(texture: Texture<Resource>, asset: IGraphicAsset): Texture<Resource>
|
protected async generateTransformedThumbnail(texture: Texture<Resource>, asset: IGraphicAsset): Promise<Texture<Resource>>
|
||||||
{
|
{
|
||||||
if(this._hasOutline)
|
|
||||||
{
|
|
||||||
const container = new NitroSprite();
|
|
||||||
const background = new NitroSprite(NitroTexture.WHITE);
|
|
||||||
|
|
||||||
background.tint = 0x000000;
|
|
||||||
background.width = (texture.width + 40);
|
|
||||||
background.height = (texture.height + 40);
|
|
||||||
|
|
||||||
const sprite = new NitroSprite(texture);
|
const sprite = new NitroSprite(texture);
|
||||||
const offsetX = ((background.width - sprite.width) / 2);
|
|
||||||
const offsetY = ((background.height - sprite.height) / 2);
|
|
||||||
|
|
||||||
sprite.position.set(offsetX, offsetY);
|
const photoContainer = new NitroSprite();
|
||||||
|
sprite.position.set(0, 0);
|
||||||
container.addChild(background, sprite);
|
photoContainer.addChild(sprite);
|
||||||
|
|
||||||
texture = TextureUtils.generateTexture(container);
|
|
||||||
}
|
|
||||||
|
|
||||||
texture.orig.width = asset.width;
|
|
||||||
texture.orig.height = asset.height;
|
|
||||||
|
|
||||||
|
const scaleFactor = (asset?.width || 64) / texture.width;
|
||||||
const matrix = new Matrix();
|
const matrix = new Matrix();
|
||||||
|
|
||||||
switch(this.direction)
|
switch (this.direction) {
|
||||||
{
|
|
||||||
case 2:
|
case 2:
|
||||||
matrix.b = -(0.5);
|
matrix.a = scaleFactor;
|
||||||
matrix.d /= 1.6;
|
matrix.b = (-0.5 * scaleFactor);
|
||||||
matrix.ty = ((0.5) * texture.width);
|
matrix.c = 0;
|
||||||
|
matrix.d = scaleFactor;
|
||||||
|
matrix.tx = 0;
|
||||||
|
matrix.ty = (0.5 * scaleFactor * texture.width);
|
||||||
break;
|
break;
|
||||||
case 0:
|
case 0:
|
||||||
case 4:
|
case 4:
|
||||||
matrix.b = (0.5);
|
matrix.a = scaleFactor;
|
||||||
matrix.d /= 1.6;
|
matrix.b = (0.5 * scaleFactor);
|
||||||
matrix.tx = -0.5;
|
matrix.c = 0;
|
||||||
|
matrix.d = scaleFactor;
|
||||||
|
matrix.tx = 0;
|
||||||
|
matrix.ty = 0;
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
matrix.a = scaleFactor;
|
||||||
|
matrix.b = 0;
|
||||||
|
matrix.c = 0;
|
||||||
|
matrix.d = scaleFactor;
|
||||||
|
matrix.tx = 0;
|
||||||
|
matrix.ty = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sprite = new NitroSprite(texture);
|
photoContainer.transform.setFromMatrix(matrix);
|
||||||
|
|
||||||
sprite.transform.setFromMatrix(matrix);
|
const width = 64;
|
||||||
|
const height = 64;
|
||||||
|
|
||||||
return TextureUtils.generateTexture(sprite);
|
const container = new NitroSprite();
|
||||||
|
|
||||||
|
photoContainer.position.set(width / 2, height / 2);
|
||||||
|
container.addChild(photoContainer);
|
||||||
|
|
||||||
|
const renderTexture = await TextureUtils.generateTexture(container, null, null, 1);
|
||||||
|
if (!renderTexture) {
|
||||||
|
console.warn('IsometricImageFurniVisualization: Failed to generate render texture for thumbnail');
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
return renderTexture;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getSpriteAssetName(scale: number, layerId: number): string
|
protected getSpriteAssetName(scale: number, layerId: number): string
|
||||||
{
|
{
|
||||||
if(this._thumbnailImageNormal && (this.getLayerTag(scale, this.direction, layerId) === IsometricImageFurniVisualization.THUMBNAIL)) return this.getThumbnailAssetName(scale);
|
if (this._thumbnailImageNormal && (this.getLayerTag(scale, this.direction, layerId) === IsometricImageFurniVisualization.THUMBNAIL)) {
|
||||||
|
return `${this.getThumbnailAssetName(scale)}-${this._uniqueId}`;
|
||||||
|
}
|
||||||
|
|
||||||
return super.getSpriteAssetName(scale, layerId);
|
return super.getSpriteAssetName(scale, layerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getThumbnailAssetName(scale: number): string
|
protected getThumbnailAssetName(scale: number): string
|
||||||
{
|
{
|
||||||
this._thumbnailAssetNameNormal = this.getFullThumbnailAssetName(this.object.id, 64);
|
return this.cacheSpriteAssetName(scale, 2, false) + this.getFrameNumber(scale, 2);
|
||||||
|
|
||||||
return this._thumbnailAssetNameNormal;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getFullThumbnailAssetName(k: number, _arg_2: number): string
|
protected getFullThumbnailAssetName(k: number, _arg_2: number): string
|
||||||
|
@ -12,31 +12,42 @@ export class PlaneTextureCache
|
|||||||
public RENDER_TEXTURE_POOL: Map<string, RenderTexture> = new Map();
|
public RENDER_TEXTURE_POOL: Map<string, RenderTexture> = new Map();
|
||||||
public RENDER_TEXTURE_CACHE: RenderTexture[] = [];
|
public RENDER_TEXTURE_CACHE: RenderTexture[] = [];
|
||||||
|
|
||||||
|
// Store an Extract instance
|
||||||
|
private _extract: Extract;
|
||||||
|
|
||||||
|
constructor()
|
||||||
|
{
|
||||||
|
// Initialize Extract with the renderer
|
||||||
|
const renderer = this.getRenderer();
|
||||||
|
if (renderer) {
|
||||||
|
this._extract = new Extract(renderer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public clearCache(): void
|
public clearCache(): void
|
||||||
{
|
{
|
||||||
this.RENDER_TEXTURE_POOL.forEach(renderTexture => renderTexture?.destroy(true));
|
this.RENDER_TEXTURE_POOL.forEach(renderTexture => renderTexture?.destroy(true));
|
||||||
|
|
||||||
this.RENDER_TEXTURE_POOL.clear();
|
this.RENDER_TEXTURE_POOL.clear();
|
||||||
this.RENDER_TEXTURE_CACHE = [];
|
this.RENDER_TEXTURE_CACHE = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public clearRenderTexture(renderTexture: RenderTexture): RenderTexture
|
public clearRenderTexture(renderTexture: RenderTexture): RenderTexture
|
||||||
{
|
{
|
||||||
if(!renderTexture) return null;
|
if (!renderTexture) return null;
|
||||||
|
|
||||||
return this.writeToRenderTexture(new Sprite(Texture.EMPTY), renderTexture);
|
return this.writeToRenderTexture(new Sprite(Texture.EMPTY), renderTexture);
|
||||||
}
|
}
|
||||||
|
|
||||||
private getTextureIdentifier(width: number, height: number, planeId: string): string
|
private getTextureIdentifier(width: number, height: number, planeId: string): string
|
||||||
{
|
{
|
||||||
return `${ planeId ?? PlaneTextureCache.DEFAULT_PLANE_ID }:${ width }:${ height }`;
|
return `${planeId ?? PlaneTextureCache.DEFAULT_PLANE_ID}:${width}:${height}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
public createRenderTexture(width: number, height: number, planeId: string = null): RenderTexture
|
public createRenderTexture(width: number, height: number, planeId: string = null): RenderTexture
|
||||||
{
|
{
|
||||||
if((width < 0) || (height < 0)) return null;
|
if (width < 0 || height < 0) return null;
|
||||||
|
|
||||||
if(!planeId)
|
if (!planeId)
|
||||||
{
|
{
|
||||||
const renderTexture = RenderTexture.create({
|
const renderTexture = RenderTexture.create({
|
||||||
width,
|
width,
|
||||||
@ -52,7 +63,7 @@ export class PlaneTextureCache
|
|||||||
|
|
||||||
let renderTexture = this.RENDER_TEXTURE_POOL.get(planeId);
|
let renderTexture = this.RENDER_TEXTURE_POOL.get(planeId);
|
||||||
|
|
||||||
if(!renderTexture)
|
if (!renderTexture)
|
||||||
{
|
{
|
||||||
renderTexture = RenderTexture.create({
|
renderTexture = RenderTexture.create({
|
||||||
width,
|
width,
|
||||||
@ -60,7 +71,6 @@ export class PlaneTextureCache
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.RENDER_TEXTURE_CACHE.push(renderTexture);
|
this.RENDER_TEXTURE_CACHE.push(renderTexture);
|
||||||
|
|
||||||
this.RENDER_TEXTURE_POOL.set(planeId, renderTexture);
|
this.RENDER_TEXTURE_POOL.set(planeId, renderTexture);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,7 +79,7 @@ export class PlaneTextureCache
|
|||||||
|
|
||||||
public createAndFillRenderTexture(width: number, height: number, planeId = null, color: number = 16777215): RenderTexture
|
public createAndFillRenderTexture(width: number, height: number, planeId = null, color: number = 16777215): RenderTexture
|
||||||
{
|
{
|
||||||
if((width < 0) || (height < 0)) return null;
|
if (width < 0 || height < 0) return null;
|
||||||
|
|
||||||
const renderTexture = this.createRenderTexture(width, height, planeId);
|
const renderTexture = this.createRenderTexture(width, height, planeId);
|
||||||
|
|
||||||
@ -78,7 +88,7 @@ export class PlaneTextureCache
|
|||||||
|
|
||||||
public createAndWriteRenderTexture(width: number, height: number, displayObject: DisplayObject, planeId: string = null, transform: Matrix = null): RenderTexture
|
public createAndWriteRenderTexture(width: number, height: number, displayObject: DisplayObject, planeId: string = null, transform: Matrix = null): RenderTexture
|
||||||
{
|
{
|
||||||
if((width < 0) || (height < 0)) return null;
|
if (width < 0 || height < 0) return null;
|
||||||
|
|
||||||
const renderTexture = this.createRenderTexture(width, height, planeId);
|
const renderTexture = this.createRenderTexture(width, height, planeId);
|
||||||
|
|
||||||
@ -87,12 +97,11 @@ export class PlaneTextureCache
|
|||||||
|
|
||||||
public clearAndFillRenderTexture(renderTexture: RenderTexture, color: number = 16777215): RenderTexture
|
public clearAndFillRenderTexture(renderTexture: RenderTexture, color: number = 16777215): RenderTexture
|
||||||
{
|
{
|
||||||
if(!renderTexture) return null;
|
if (!renderTexture) return null;
|
||||||
|
|
||||||
const sprite = new Sprite(Texture.WHITE);
|
const sprite = new Sprite(Texture.WHITE);
|
||||||
|
|
||||||
sprite.tint = color;
|
sprite.tint = color;
|
||||||
|
|
||||||
sprite.width = renderTexture.width;
|
sprite.width = renderTexture.width;
|
||||||
sprite.height = renderTexture.height;
|
sprite.height = renderTexture.height;
|
||||||
|
|
||||||
@ -101,7 +110,7 @@ export class PlaneTextureCache
|
|||||||
|
|
||||||
public writeToRenderTexture(displayObject: DisplayObject, renderTexture: RenderTexture, clear: boolean = true, transform: Matrix = null): RenderTexture
|
public writeToRenderTexture(displayObject: DisplayObject, renderTexture: RenderTexture, clear: boolean = true, transform: Matrix = null): RenderTexture
|
||||||
{
|
{
|
||||||
if(!displayObject || !renderTexture) return null;
|
if (!displayObject || !renderTexture) return null;
|
||||||
|
|
||||||
this.getRenderer().render(displayObject, {
|
this.getRenderer().render(displayObject, {
|
||||||
renderTexture,
|
renderTexture,
|
||||||
@ -114,7 +123,7 @@ export class PlaneTextureCache
|
|||||||
|
|
||||||
public getPixels(displayObject: DisplayObject | RenderTexture, frame: Rectangle = null): Uint8Array
|
public getPixels(displayObject: DisplayObject | RenderTexture, frame: Rectangle = null): Uint8Array
|
||||||
{
|
{
|
||||||
return this.getExtractor().pixels(displayObject);
|
return this.getExtractor().pixels(displayObject, frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getRenderer(): Renderer | AbstractRenderer
|
public getRenderer(): Renderer | AbstractRenderer
|
||||||
@ -124,6 +133,10 @@ export class PlaneTextureCache
|
|||||||
|
|
||||||
public getExtractor(): Extract
|
public getExtractor(): Extract
|
||||||
{
|
{
|
||||||
return (this.getRenderer().plugins.extract as Extract);
|
// Return the stored Extract instance
|
||||||
|
if (!this._extract) {
|
||||||
|
throw new Error('Extract plugin not initialized. Ensure renderer is available.');
|
||||||
|
}
|
||||||
|
return this._extract;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -8,11 +8,57 @@ import { PixiApplicationProxy } from './PixiApplicationProxy';
|
|||||||
|
|
||||||
export class TextureUtils
|
export class TextureUtils
|
||||||
{
|
{
|
||||||
|
private static _extract: Extract | null = null;
|
||||||
|
|
||||||
|
public static initialize(renderer: Renderer | AbstractRenderer): void
|
||||||
|
{
|
||||||
|
if (!this._extract && renderer) {
|
||||||
|
this._extract = new Extract(renderer);
|
||||||
|
console.log('TextureUtils: Initialized Extract plugin', { renderer });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async generateImage(target: DisplayObject | RenderTexture): Promise<HTMLImageElement>
|
||||||
|
{
|
||||||
|
if (!target) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const extractor = this.getExtractor();
|
||||||
|
if (!extractor) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const image = await extractor.image(target);
|
||||||
|
|
||||||
|
if (!image || !image.src || typeof image.src !== 'string' || !image.src.startsWith('data:image/')) {
|
||||||
|
const canvas = extractor.canvas(target);
|
||||||
|
if (canvas) {
|
||||||
|
const dataUrl = canvas.toDataURL('image/png');
|
||||||
|
if (dataUrl && dataUrl.startsWith('data:image/')) {
|
||||||
|
const fallbackImage = new Image();
|
||||||
|
fallbackImage.src = dataUrl;
|
||||||
|
return fallbackImage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const fallback = new Image();
|
||||||
|
fallback.src = '';
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
return image;
|
||||||
|
} catch (error) {
|
||||||
|
const fallback = new Image();
|
||||||
|
fallback.src = '';
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static generateTexture(displayObject: DisplayObject, region: Rectangle = null, scaleMode: number = null, resolution: number = 1): RenderTexture
|
public static generateTexture(displayObject: DisplayObject, region: Rectangle = null, scaleMode: number = null, resolution: number = 1): RenderTexture
|
||||||
{
|
{
|
||||||
if(!displayObject) return null;
|
if (!displayObject) return null;
|
||||||
|
|
||||||
if(scaleMode === null) scaleMode = settings.SCALE_MODE;
|
if (scaleMode === null) scaleMode = settings.SCALE_MODE;
|
||||||
|
|
||||||
return this.getRenderer().generateTexture(displayObject, {
|
return this.getRenderer().generateTexture(displayObject, {
|
||||||
scaleMode,
|
scaleMode,
|
||||||
@ -23,42 +69,70 @@ export class TextureUtils
|
|||||||
|
|
||||||
public static generateTextureFromImage(image: HTMLImageElement): Texture<Resource>
|
public static generateTextureFromImage(image: HTMLImageElement): Texture<Resource>
|
||||||
{
|
{
|
||||||
if(!image) return null;
|
if (!image) return null;
|
||||||
|
|
||||||
return Texture.from(image);
|
return Texture.from(image);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static generateImage(target: DisplayObject | RenderTexture): HTMLImageElement
|
public static async generateImageUrl(target: DisplayObject | RenderTexture): Promise<string>
|
||||||
{
|
{
|
||||||
if(!target) return null;
|
if (!target) {
|
||||||
|
return null;
|
||||||
return this.getExtractor().image(target);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static generateImageUrl(target: DisplayObject | RenderTexture): string
|
const extractor = this.getExtractor();
|
||||||
{
|
if (!extractor) {
|
||||||
if(!target) return null;
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return this.getExtractor().base64(target);
|
let base64: string | Promise<string> = extractor.base64(target);
|
||||||
|
|
||||||
|
if (base64 && typeof base64 === 'object' && 'then' in base64) {
|
||||||
|
try {
|
||||||
|
base64 = await base64;
|
||||||
|
} catch (error) {
|
||||||
|
base64 = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!base64 || typeof base64 !== 'string' || !base64.startsWith('data:image/')) {
|
||||||
|
const canvas = extractor.canvas(target);
|
||||||
|
if (canvas) {
|
||||||
|
const dataUrl = canvas.toDataURL('image/png');
|
||||||
|
if (dataUrl && typeof dataUrl === 'string' && dataUrl.startsWith('data:image/')) {
|
||||||
|
return dataUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base64;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static generateCanvas(target: DisplayObject | RenderTexture): HTMLCanvasElement
|
public static generateCanvas(target: DisplayObject | RenderTexture): HTMLCanvasElement
|
||||||
{
|
{
|
||||||
if(!target) return null;
|
if (!target) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return this.getExtractor().canvas(target);
|
const extractor = this.getExtractor();
|
||||||
|
if (!extractor) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return extractor.canvas(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static clearRenderTexture(renderTexture: RenderTexture): RenderTexture
|
public static clearRenderTexture(renderTexture: RenderTexture): RenderTexture
|
||||||
{
|
{
|
||||||
if(!renderTexture) return null;
|
if (!renderTexture) return null;
|
||||||
|
|
||||||
return this.writeToRenderTexture(new Sprite(Texture.EMPTY), renderTexture);
|
return this.writeToRenderTexture(new Sprite(Texture.EMPTY), renderTexture);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static createRenderTexture(width: number, height: number): RenderTexture
|
public static createRenderTexture(width: number, height: number): RenderTexture
|
||||||
{
|
{
|
||||||
if((width < 0) || (height < 0)) return null;
|
if (width < 0 || height < 0) return null;
|
||||||
|
|
||||||
return RenderTexture.create({
|
return RenderTexture.create({
|
||||||
width,
|
width,
|
||||||
@ -68,7 +142,7 @@ export class TextureUtils
|
|||||||
|
|
||||||
public static createAndFillRenderTexture(width: number, height: number, color: number = 16777215): RenderTexture
|
public static createAndFillRenderTexture(width: number, height: number, color: number = 16777215): RenderTexture
|
||||||
{
|
{
|
||||||
if((width < 0) || (height < 0)) return null;
|
if (width < 0 || height < 0) return null;
|
||||||
|
|
||||||
const renderTexture = this.createRenderTexture(width, height);
|
const renderTexture = this.createRenderTexture(width, height);
|
||||||
|
|
||||||
@ -77,7 +151,7 @@ export class TextureUtils
|
|||||||
|
|
||||||
public static createAndWriteRenderTexture(width: number, height: number, displayObject: DisplayObject, transform: Matrix = null): RenderTexture
|
public static createAndWriteRenderTexture(width: number, height: number, displayObject: DisplayObject, transform: Matrix = null): RenderTexture
|
||||||
{
|
{
|
||||||
if((width < 0) || (height < 0)) return null;
|
if (width < 0 || height < 0) return null;
|
||||||
|
|
||||||
const renderTexture = this.createRenderTexture(width, height);
|
const renderTexture = this.createRenderTexture(width, height);
|
||||||
|
|
||||||
@ -86,12 +160,11 @@ export class TextureUtils
|
|||||||
|
|
||||||
public static clearAndFillRenderTexture(renderTexture: RenderTexture, color: number = 16777215): RenderTexture
|
public static clearAndFillRenderTexture(renderTexture: RenderTexture, color: number = 16777215): RenderTexture
|
||||||
{
|
{
|
||||||
if(!renderTexture) return null;
|
if (!renderTexture) return null;
|
||||||
|
|
||||||
const sprite = new Sprite(Texture.WHITE);
|
const sprite = new Sprite(Texture.WHITE);
|
||||||
|
|
||||||
sprite.tint = color;
|
sprite.tint = color;
|
||||||
|
|
||||||
sprite.width = renderTexture.width;
|
sprite.width = renderTexture.width;
|
||||||
sprite.height = renderTexture.height;
|
sprite.height = renderTexture.height;
|
||||||
|
|
||||||
@ -100,7 +173,7 @@ export class TextureUtils
|
|||||||
|
|
||||||
public static writeToRenderTexture(displayObject: DisplayObject, renderTexture: RenderTexture, clear: boolean = true, transform: Matrix = null): RenderTexture
|
public static writeToRenderTexture(displayObject: DisplayObject, renderTexture: RenderTexture, clear: boolean = true, transform: Matrix = null): RenderTexture
|
||||||
{
|
{
|
||||||
if(!displayObject || !renderTexture) return null;
|
if (!displayObject || !renderTexture) return null;
|
||||||
|
|
||||||
this.getRenderer().render(displayObject, {
|
this.getRenderer().render(displayObject, {
|
||||||
renderTexture,
|
renderTexture,
|
||||||
@ -113,16 +186,33 @@ export class TextureUtils
|
|||||||
|
|
||||||
public static getPixels(displayObject: DisplayObject | RenderTexture, frame: Rectangle = null): Uint8Array
|
public static getPixels(displayObject: DisplayObject | RenderTexture, frame: Rectangle = null): Uint8Array
|
||||||
{
|
{
|
||||||
return this.getExtractor().pixels(displayObject);
|
const extractor = this.getExtractor();
|
||||||
|
if (!extractor) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return extractor.pixels(displayObject, frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static getRenderer(): Renderer | AbstractRenderer
|
public static getRenderer(): Renderer | AbstractRenderer
|
||||||
{
|
{
|
||||||
return PixiApplicationProxy.instance.renderer;
|
const renderer = PixiApplicationProxy.instance.renderer;
|
||||||
|
if (!renderer) {
|
||||||
|
console.warn('getRenderer: Renderer not available');
|
||||||
|
}
|
||||||
|
return renderer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static getExtractor(): Extract
|
public static getExtractor(): Extract
|
||||||
{
|
{
|
||||||
return (this.getRenderer().plugins.extract as Extract);
|
if (!this._extract) {
|
||||||
|
const renderer = this.getRenderer();
|
||||||
|
if (renderer) {
|
||||||
|
this._extract = new Extract(renderer);
|
||||||
|
} else {
|
||||||
|
throw new Error('Cannot initialize Extract: Renderer not available');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this._extract;
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user