feat: Add Flip X and Flip Y options for UV baking
Build and Deploy / build-and-push (push) Successful in 1m56s
Build and Deploy / build-and-push (push) Successful in 1m56s
This commit is contained in:
@@ -7,7 +7,7 @@ import { useTexturePipeline } from "../hooks/useTexturePipeline";
|
|||||||
interface ModelViewerProps {
|
interface ModelViewerProps {
|
||||||
object: THREE.Object3D | null;
|
object: THREE.Object3D | null;
|
||||||
sceneObjects: any[];
|
sceneObjects: any[];
|
||||||
bakeRequest?: { id: string, type: 'planar' | 'cylinder' | 'sphere' | 'box', timestamp: number } | null;
|
bakeRequest?: { id: string, type: 'planar' | 'cylinder' | 'sphere' | 'box', flipX?: boolean, flipY?: boolean, timestamp: number } | null;
|
||||||
onBakeComplete?: (dataUrl: string) => void;
|
onBakeComplete?: (dataUrl: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,7 +271,7 @@ export const ModelViewer = forwardRef<THREE.Group, ModelViewerProps>(({ object,
|
|||||||
|
|
||||||
// Bake
|
// Bake
|
||||||
try {
|
try {
|
||||||
const dataUrl = bakeTextureToNewUVs(targetMesh, oldUVArray, tex, gl, 1024);
|
const dataUrl = bakeTextureToNewUVs(targetMesh, oldUVArray, tex, gl, 1024, bakeRequest.flipX, bakeRequest.flipY);
|
||||||
onBakeComplete(dataUrl);
|
onBakeComplete(dataUrl);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Baking failed", e);
|
console.error("Baking failed", e);
|
||||||
|
|||||||
+13
-4
@@ -100,7 +100,9 @@ export function bakeTextureToNewUVs(
|
|||||||
oldUVArray: Float32Array,
|
oldUVArray: Float32Array,
|
||||||
oldTexture: THREE.Texture,
|
oldTexture: THREE.Texture,
|
||||||
renderer: THREE.WebGLRenderer,
|
renderer: THREE.WebGLRenderer,
|
||||||
resolution = 1024
|
resolution = 1024,
|
||||||
|
flipX = false,
|
||||||
|
flipY = false
|
||||||
): string {
|
): string {
|
||||||
// Store original geometry attributes
|
// Store original geometry attributes
|
||||||
const geometry = mesh.geometry.clone();
|
const geometry = mesh.geometry.clone();
|
||||||
@@ -113,15 +115,22 @@ export function bakeTextureToNewUVs(
|
|||||||
// Color is sampled from the old texture using the old UVs (uv2)
|
// Color is sampled from the old texture using the old UVs (uv2)
|
||||||
const bakeMaterial = new THREE.ShaderMaterial({
|
const bakeMaterial = new THREE.ShaderMaterial({
|
||||||
uniforms: {
|
uniforms: {
|
||||||
tDiffuse: { value: oldTexture }
|
tDiffuse: { value: oldTexture },
|
||||||
|
uFlipX: { value: flipX ? 1.0 : 0.0 },
|
||||||
|
uFlipY: { value: flipY ? 1.0 : 0.0 }
|
||||||
},
|
},
|
||||||
vertexShader: `
|
vertexShader: `
|
||||||
attribute vec2 uv2;
|
attribute vec2 uv2;
|
||||||
varying vec2 vOldUV;
|
varying vec2 vOldUV;
|
||||||
|
uniform float uFlipX;
|
||||||
|
uniform float uFlipY;
|
||||||
void main() {
|
void main() {
|
||||||
vOldUV = uv2;
|
vOldUV = uv2;
|
||||||
// Flip X and Y to fix upside down and reversed texture
|
|
||||||
gl_Position = vec4(1.0 - uv.x * 2.0, 1.0 - uv.y * 2.0, 0.0, 1.0);
|
float finalX = mix(uv.x * 2.0 - 1.0, 1.0 - uv.x * 2.0, uFlipX);
|
||||||
|
float finalY = mix(uv.y * 2.0 - 1.0, 1.0 - uv.y * 2.0, uFlipY);
|
||||||
|
|
||||||
|
gl_Position = vec4(finalX, finalY, 0.0, 1.0);
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
fragmentShader: `
|
fragmentShader: `
|
||||||
|
|||||||
+19
-3
@@ -642,7 +642,8 @@ export default function Editor() {
|
|||||||
const [renderProgress, setRenderProgress] = useState<{ step: string; progress: number } | null>(null);
|
const [renderProgress, setRenderProgress] = useState<{ step: string; progress: number } | null>(null);
|
||||||
const [isRenderComplete, setIsRenderComplete] = useState(false);
|
const [isRenderComplete, setIsRenderComplete] = useState(false);
|
||||||
const [bakedDataUrl, setBakedDataUrl] = useState<string | null>(null);
|
const [bakedDataUrl, setBakedDataUrl] = useState<string | null>(null);
|
||||||
const [bakeRequest, setBakeRequest] = useState<{ id: string, type: 'planar' | 'cylinder' | 'sphere' | 'box', timestamp: number } | null>(null);
|
const [bakeOptions, setBakeOptions] = useState({ flipX: false, flipY: false });
|
||||||
|
const [bakeRequest, setBakeRequest] = useState<{ id: string, type: 'planar' | 'cylinder' | 'sphere' | 'box', flipX: boolean, flipY: boolean, timestamp: number } | null>(null);
|
||||||
const [importStatus, setImportStatus] = useState<string | null>(null);
|
const [importStatus, setImportStatus] = useState<string | null>(null);
|
||||||
const [importError, setImportError] = useState<string | null>(null);
|
const [importError, setImportError] = useState<string | null>(null);
|
||||||
const [modelData, setModelData] = useState<{ name: string, data: string, extension: string } | null>(null);
|
const [modelData, setModelData] = useState<{ name: string, data: string, extension: string } | null>(null);
|
||||||
@@ -4687,19 +4688,34 @@ export default function Editor() {
|
|||||||
<option value="triplanar">Triplanar</option>
|
<option value="triplanar">Triplanar</option>
|
||||||
</select>
|
</select>
|
||||||
{selectedObject.materialProps?.mappingType && selectedObject.materialProps.mappingType !== 'uv' && selectedObject.materialProps.mappingType !== 'triplanar' && (
|
{selectedObject.materialProps?.mappingType && selectedObject.materialProps.mappingType !== 'uv' && selectedObject.materialProps.mappingType !== 'triplanar' && (
|
||||||
|
<div className="flex flex-col gap-2 mt-2 bg-zinc-800/50 p-2 rounded border border-zinc-700/50">
|
||||||
|
<span className="text-[10px] text-zinc-400 font-bold mb-1">Baking Options</span>
|
||||||
|
<div className="flex gap-4">
|
||||||
|
<label className="flex items-center gap-1.5 cursor-pointer">
|
||||||
|
<input type="checkbox" checked={bakeOptions.flipX} onChange={e => setBakeOptions(p => ({...p, flipX: e.target.checked}))} className="rounded bg-zinc-900 border-zinc-700 text-blue-500 w-3 h-3" />
|
||||||
|
<span className="text-[10px] text-zinc-300">Flip X</span>
|
||||||
|
</label>
|
||||||
|
<label className="flex items-center gap-1.5 cursor-pointer">
|
||||||
|
<input type="checkbox" checked={bakeOptions.flipY} onChange={e => setBakeOptions(p => ({...p, flipY: e.target.checked}))} className="rounded bg-zinc-900 border-zinc-700 text-blue-500 w-3 h-3" />
|
||||||
|
<span className="text-[10px] text-zinc-300">Flip Y</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!selectedObject.materialProps?.map) return;
|
if (!selectedObject.materialProps?.map) return;
|
||||||
setBakeRequest({
|
setBakeRequest({
|
||||||
id: selectedObject.id,
|
id: selectedObject.id,
|
||||||
type: selectedObject.materialProps.mappingType as any,
|
type: selectedObject.materialProps.mappingType as any,
|
||||||
|
flipX: bakeOptions.flipX,
|
||||||
|
flipY: bakeOptions.flipY,
|
||||||
timestamp: Date.now()
|
timestamp: Date.now()
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
className="w-full py-2 bg-orange-500 hover:bg-orange-600 text-white font-bold rounded shadow transition-colors mt-1 text-[11px]"
|
className="w-full py-2 bg-orange-500 hover:bg-orange-600 text-white font-bold rounded shadow transition-colors text-[11px] mt-1"
|
||||||
>
|
>
|
||||||
{bakeRequest?.id === selectedObject.id ? 'Baking...' : 'Bake to UVs (Required for 2D AI)'}
|
{bakeRequest?.id === selectedObject.id ? 'Baking...' : 'Bake to UVs (For 2D AI)'}
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user