cdn and packages building
This commit is contained in:
+47
-10
@@ -1,33 +1,70 @@
|
||||
{
|
||||
"name": "@pascal-app/core",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"version": "0.1.5",
|
||||
"description": "Core library for Pascal 3D building editor",
|
||||
"type": "module",
|
||||
"main": "./src/index.ts",
|
||||
"types": "./src/index.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./src/index.ts",
|
||||
"import": "./src/index.ts",
|
||||
"default": "./src/index.ts"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"README.md"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsc --declaration --emitDeclarationOnly && tsc",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"publishConfig": {
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js",
|
||||
"default": "./dist/index.js"
|
||||
}
|
||||
}
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@react-three/drei": "^10",
|
||||
"@react-three/fiber": "^9",
|
||||
"react": "^18 || ^19",
|
||||
"three": "^0.182"
|
||||
},
|
||||
"dependencies": {
|
||||
"dedent": "^1.7.1",
|
||||
"idb-keyval": "^6.2.2",
|
||||
"mitt": "^3.0.1",
|
||||
"nanoid": "^5.1.6",
|
||||
"three-bvh-csg": "^0.0.17",
|
||||
"zod": "^4.3.5",
|
||||
"zundo": "^2.3.0",
|
||||
"zustand": "^5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@repo/typescript-config": "*",
|
||||
"@types/react": "^19.2.2",
|
||||
"typescript": "5.9.2",
|
||||
"@types/three": "^0.182.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"dedent": "^1.7.1",
|
||||
"idb-keyval": "^6.2.2",
|
||||
"mitt": "^3.0.1",
|
||||
"nanoid": "^5.1.6",
|
||||
"zod": "^4.3.5",
|
||||
"zundo": "^2.3.0"
|
||||
}
|
||||
"keywords": [
|
||||
"3d",
|
||||
"building",
|
||||
"editor",
|
||||
"architecture",
|
||||
"webgpu",
|
||||
"three.js"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/your-username/pascal-editor.git",
|
||||
"directory": "packages/core"
|
||||
},
|
||||
"license": "MIT"
|
||||
}
|
||||
|
||||
@@ -4,6 +4,6 @@
|
||||
"outDir": "dist",
|
||||
"rootDir": "src"
|
||||
},
|
||||
"include": ["src", "../viewer/src/systems/level"],
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
|
||||
@@ -1,26 +1,66 @@
|
||||
{
|
||||
"name": "@pascal-app/viewer",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"version": "0.1.5",
|
||||
"description": "3D viewer component for Pascal building editor",
|
||||
"type": "module",
|
||||
"main": "./src/index.ts",
|
||||
"types": "./src/index.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./src/index.ts",
|
||||
"import": "./src/index.ts",
|
||||
"default": "./src/index.ts"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"README.md"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsc --declaration --emitDeclarationOnly && tsc",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"publishConfig": {
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js",
|
||||
"default": "./dist/index.js"
|
||||
}
|
||||
}
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@pascal-app/core": "*",
|
||||
"@pascal-app/core": "^0.1.4",
|
||||
"@react-three/drei": "^10",
|
||||
"@react-three/fiber": "^9",
|
||||
"react": "^18 || ^19",
|
||||
"three": "^0.182"
|
||||
},
|
||||
"dependencies": {
|
||||
"zustand": "^5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@repo/typescript-config": "*",
|
||||
"@types/react": "^19.2.2",
|
||||
"typescript": "5.9.2",
|
||||
"@types/three": "^0.182.0"
|
||||
}
|
||||
},
|
||||
"keywords": [
|
||||
"3d",
|
||||
"building",
|
||||
"editor",
|
||||
"viewer",
|
||||
"architecture",
|
||||
"webgpu",
|
||||
"three.js",
|
||||
"react-three-fiber"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/your-username/pascal-editor.git",
|
||||
"directory": "packages/viewer"
|
||||
},
|
||||
"license": "MIT"
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useGLTF } from '@react-three/drei/core/Gltf'
|
||||
import { Suspense, useEffect, useMemo, useRef } from 'react'
|
||||
import type { Group, Material, Mesh } from 'three'
|
||||
import { DoubleSide, MeshStandardNodeMaterial } from 'three/webgpu'
|
||||
import { resolveCdnUrl } from '../../../lib/asset-url'
|
||||
import { useNodeEvents } from '../../../hooks/use-node-events'
|
||||
|
||||
// Shared materials to avoid creating new instances for every mesh
|
||||
@@ -46,7 +47,7 @@ export const ItemRenderer = ({ node }: { node: ItemNode }) => {
|
||||
}
|
||||
|
||||
const ModelRenderer = ({ node }: { node: ItemNode }) => {
|
||||
const { scene, nodes } = useGLTF(node.asset.src)
|
||||
const { scene, nodes } = useGLTF(resolveCdnUrl(node.asset.src) || '')
|
||||
|
||||
if (nodes.cutout) {
|
||||
nodes.cutout.visible = false
|
||||
@@ -64,19 +65,19 @@ const ModelRenderer = ({ node }: { node: ItemNode }) => {
|
||||
if ((child as Mesh).isMesh) {
|
||||
const mesh = child as Mesh
|
||||
if (mesh.name === 'cutout') {
|
||||
child.visible = false;
|
||||
child.visible = false
|
||||
return
|
||||
}
|
||||
|
||||
let hasGlass = false;
|
||||
let hasGlass = false
|
||||
|
||||
// Handle both single material and material array cases
|
||||
if (Array.isArray(mesh.material)) {
|
||||
mesh.material = mesh.material.map((mat) => getMaterialForOriginal(mat))
|
||||
hasGlass = mesh.material.some(mat => mat.name === 'glass');
|
||||
hasGlass = mesh.material.some((mat) => mat.name === 'glass')
|
||||
} else {
|
||||
mesh.material = getMaterialForOriginal(mesh.material)
|
||||
hasGlass = mesh.material.name === 'glass';
|
||||
hasGlass = mesh.material.name === 'glass'
|
||||
}
|
||||
mesh.castShadow = !hasGlass
|
||||
mesh.receiveShadow = !hasGlass
|
||||
|
||||
@@ -27,7 +27,8 @@ export const ScanRenderer = ({ node }: { node: ScanNode }) => {
|
||||
}
|
||||
|
||||
const ScanModel = ({ url, opacity }: { url: string; opacity: number }) => {
|
||||
const { scene } = useGLTFKTX2(url)
|
||||
const gltf = useGLTFKTX2(url) as any
|
||||
const scene = gltf.scene
|
||||
|
||||
useMemo(() => {
|
||||
const normalizedOpacity = opacity / 100
|
||||
@@ -44,7 +45,7 @@ const ScanModel = ({ url, opacity }: { url: string; opacity: number }) => {
|
||||
material.needsUpdate = true
|
||||
}
|
||||
|
||||
scene.traverse((child) => {
|
||||
scene.traverse((child: any) => {
|
||||
if ((child as Mesh).isMesh) {
|
||||
const mesh = child as Mesh
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import { MeshoptDecoder } from "three/examples/jsm/libs/meshopt_decoder.module.j
|
||||
const ktx2LoaderInstance = new KTX2Loader()
|
||||
ktx2LoaderInstance.setTranscoderPath('https://cdn.jsdelivr.net/gh/pmndrs/drei-assets@master/basis/')
|
||||
|
||||
const useGLTFKTX2 = (path: string) => {
|
||||
const useGLTFKTX2 = (path: string): ReturnType<typeof useGLTF> => {
|
||||
const gl = useThree((state) => state.gl)
|
||||
|
||||
return useGLTF(path, true, true, (loader) => {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export { default as Viewer } from './components/viewer'
|
||||
export { useGridEvents } from './hooks/use-grid-events'
|
||||
export { default as useViewer } from './store/use-viewer'
|
||||
export { ASSETS_CDN_URL, resolveAssetUrl, resolveCdnUrl } from './lib/asset-url'
|
||||
@@ -0,0 +1,51 @@
|
||||
import { loadAssetUrl } from '@pascal-app/core'
|
||||
|
||||
export const ASSETS_CDN_URL = 'https://pascal-cdn.wawasensei.dev'
|
||||
|
||||
/**
|
||||
* Resolves an asset URL to the appropriate format:
|
||||
* - If URL starts with http:// or https://, return as-is (external URL)
|
||||
* - If URL starts with asset://, resolve from IndexedDB storage
|
||||
* - If URL starts with /, prepend CDN URL (absolute path)
|
||||
* - Otherwise, prepend CDN URL (relative path)
|
||||
*/
|
||||
export async function resolveAssetUrl(url: string | undefined | null): Promise<string | null> {
|
||||
if (!url) return null
|
||||
|
||||
// External URL - use as-is
|
||||
if (url.startsWith('http://') || url.startsWith('https://')) {
|
||||
return url
|
||||
}
|
||||
|
||||
// IndexedDB asset - resolve from storage
|
||||
if (url.startsWith('asset://')) {
|
||||
return loadAssetUrl(url)
|
||||
}
|
||||
|
||||
// Absolute or relative path - prepend CDN URL
|
||||
const normalizedPath = url.startsWith('/') ? url : `/${url}`
|
||||
return `${ASSETS_CDN_URL}${normalizedPath}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronous version for URLs that don't need IndexedDB resolution
|
||||
* Only use this if you're sure the URL is not an asset:// URL
|
||||
*/
|
||||
export function resolveCdnUrl(url: string | undefined | null): string | null {
|
||||
if (!url) return null
|
||||
|
||||
// External URL - use as-is
|
||||
if (url.startsWith('http://') || url.startsWith('https://')) {
|
||||
return url
|
||||
}
|
||||
|
||||
// Don't use this for asset:// URLs - use resolveAssetUrl instead
|
||||
if (url.startsWith('asset://')) {
|
||||
console.warn('Use resolveAssetUrl() for asset:// URLs, not resolveCdnUrl()')
|
||||
return null
|
||||
}
|
||||
|
||||
// Absolute or relative path - prepend CDN URL
|
||||
const normalizedPath = url.startsWith('/') ? url : `/${url}`
|
||||
return `${ASSETS_CDN_URL}${normalizedPath}`
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
import type { AnyNode, BaseNode, BuildingNode, LevelNode, ZoneNode } from "@pascal-app/core";
|
||||
import type { Object3D } from "three";
|
||||
type SelectionPath = {
|
||||
buildingId: BuildingNode["id"] | null;
|
||||
levelId: LevelNode["id"] | null;
|
||||
zoneId: ZoneNode["id"] | null;
|
||||
selectedIds: BaseNode["id"][];
|
||||
};
|
||||
type Outliner = {
|
||||
selectedObjects: Object3D[];
|
||||
hoveredObjects: Object3D[];
|
||||
};
|
||||
type ViewerState = {
|
||||
selection: SelectionPath;
|
||||
hoveredId: AnyNode['id'] | ZoneNode['id'] | null;
|
||||
setHoveredId: (id: AnyNode['id'] | ZoneNode['id'] | null) => void;
|
||||
cameraMode: 'perspective' | 'orthographic';
|
||||
setCameraMode: (mode: 'perspective' | 'orthographic') => void;
|
||||
levelMode: 'stacked' | 'exploded' | 'solo' | 'manual';
|
||||
setLevelMode: (mode: 'stacked' | 'exploded' | 'solo' | 'manual') => void;
|
||||
wallMode: 'up' | 'cutaway' | 'down';
|
||||
setWallMode: (mode: 'up' | 'cutaway' | 'down') => void;
|
||||
showScans: boolean;
|
||||
setShowScans: (show: boolean) => void;
|
||||
showGuides: boolean;
|
||||
setShowGuides: (show: boolean) => void;
|
||||
setSelection: (updates: Partial<SelectionPath>) => void;
|
||||
resetSelection: () => void;
|
||||
outliner: Outliner;
|
||||
exportScene: (() => Promise<void>) | null;
|
||||
setExportScene: (fn: (() => Promise<void>) | null) => void;
|
||||
};
|
||||
declare const useViewer: import("zustand").UseBoundStore<import("zustand").StoreApi<ViewerState>>;
|
||||
export default useViewer;
|
||||
//# sourceMappingURL=use-viewer.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"use-viewer.d.ts","sourceRoot":"","sources":["use-viewer.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,OAAO,EACP,QAAQ,EACR,YAAY,EACZ,SAAS,EACT,QAAQ,EACT,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAItC,KAAK,aAAa,GAAG;IACnB,UAAU,EAAE,YAAY,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACtC,OAAO,EAAE,SAAS,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAChC,MAAM,EAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC9B,WAAW,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;CAC/B,CAAC;AAEF,KAAK,QAAQ,GAAG;IACd,eAAe,EAAE,QAAQ,EAAE,CAAC;IAC5B,cAAc,EAAE,QAAQ,EAAE,CAAC;CAC5B,CAAC;AAEF,KAAK,WAAW,GAAG;IACjB,SAAS,EAAE,aAAa,CAAA;IACxB,SAAS,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IAChD,YAAY,EAAE,CAAC,EAAE,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,IAAI,KAAK,IAAI,CAAA;IAEjE,UAAU,EAAE,aAAa,GAAG,cAAc,CAAA;IAC1C,aAAa,EAAE,CAAC,IAAI,EAAE,aAAa,GAAG,cAAc,KAAK,IAAI,CAAA;IAE7D,SAAS,EAAE,SAAS,GAAG,UAAU,GAAG,MAAM,GAAG,QAAQ,CAAA;IACrD,YAAY,EAAE,CAAC,IAAI,EAAE,SAAS,GAAG,UAAU,GAAG,MAAM,GAAG,QAAQ,KAAK,IAAI,CAAA;IAExE,QAAQ,EAAE,IAAI,GAAG,SAAS,GAAG,MAAM,CAAA;IACnC,WAAW,EAAE,CAAC,IAAI,EAAE,IAAI,GAAG,SAAS,GAAG,MAAM,KAAK,IAAI,CAAA;IAEtD,SAAS,EAAE,OAAO,CAAA;IAClB,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IAErC,UAAU,EAAE,OAAO,CAAA;IACnB,aAAa,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IAGtC,YAAY,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,aAAa,CAAC,KAAK,IAAI,CAAA;IACvD,cAAc,EAAE,MAAM,IAAI,CAAA;IAE1B,QAAQ,EAAE,QAAQ,CAAA;IAGlB,WAAW,EAAE,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,CAAA;IACzC,cAAc,EAAE,CAAC,EAAE,EAAE,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,KAAK,IAAI,CAAA;CAC3D,CAAA;AAED,QAAA,MAAM,SAAS,0EAqDZ,CAAC;AAEJ,eAAe,SAAS,CAAC"}
|
||||
@@ -0,0 +1,2 @@
|
||||
export declare const LevelSystem: () => null;
|
||||
//# sourceMappingURL=level-system.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"level-system.d.ts","sourceRoot":"","sources":["level-system.tsx"],"names":[],"mappings":"AAQA,eAAO,MAAM,WAAW,YAkBvB,CAAA"}
|
||||
Reference in New Issue
Block a user