Add custom coin geometries
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
import { useGLTF, useTexture } from "@react-three/drei";
|
||||
import { CoinProps } from "@/types/coin";
|
||||
|
||||
export function BahamasCents10(coin: CoinProps) {
|
||||
// @ts-expect-error property `nodes` actually exists
|
||||
const { nodes } = useGLTF("/models/bahamas-cents-10.gltf");
|
||||
const texture = useTexture("/api/merge-textures/" + coin.id);
|
||||
|
||||
return (
|
||||
<>
|
||||
<primitive object={nodes.BahamasCents10.geometry} />
|
||||
<meshStandardMaterial metalness={1} roughness={0.5} map={texture} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
useGLTF.preload("/models/bahamas-cents-10.gltf");
|
||||
@@ -0,0 +1,17 @@
|
||||
import { useGLTF, useTexture } from "@react-three/drei";
|
||||
import { CoinProps } from "@/types/coin";
|
||||
|
||||
export function BahamasCents15(coin: CoinProps) {
|
||||
// @ts-expect-error property `nodes` actually exists
|
||||
const { nodes } = useGLTF("/models/bahamas-cents-15.gltf");
|
||||
const texture = useTexture("/api/merge-textures/" + coin.id);
|
||||
|
||||
return (
|
||||
<>
|
||||
<primitive object={nodes.BahamasCents15.geometry} />
|
||||
<meshStandardMaterial metalness={1} roughness={0.5} map={texture} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
useGLTF.preload("/models/bahamas-cents-15.gltf");
|
||||
@@ -0,0 +1,17 @@
|
||||
import { useGLTF, useTexture } from "@react-three/drei";
|
||||
import { CoinProps } from "@/types/coin";
|
||||
|
||||
export function EuroCents20(coin: CoinProps) {
|
||||
// @ts-expect-error property `nodes` actually exists
|
||||
const { nodes } = useGLTF("/models/euro-cents-20.gltf");
|
||||
const texture = useTexture("/api/merge-textures/" + coin.id);
|
||||
|
||||
return (
|
||||
<>
|
||||
<primitive object={nodes.EuroCents20.geometry} />
|
||||
<meshStandardMaterial metalness={1} roughness={0.5} map={texture} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
useGLTF.preload("/models/euro-cents-20.gltf");
|
||||
@@ -2,6 +2,9 @@ import * as THREE from "three";
|
||||
import { useLoader } from "@react-three/fiber";
|
||||
import { CoinProps } from "@/types/coin";
|
||||
import { useEffect, useMemo } from "react";
|
||||
import { EuroCents20 } from "./custom-geometries/euro-cents-20";
|
||||
import { BahamasCents15 } from "./custom-geometries/bahamas-cents-15";
|
||||
import { BahamasCents10 } from "./custom-geometries/bahamas-cents-10";
|
||||
|
||||
const SafeEdgeMaterial = ({ edge }: Pick<CoinProps, "edge">) => {
|
||||
const edgeTexture: THREE.Texture = useLoader(
|
||||
@@ -23,10 +26,8 @@ const SafeEdgeMaterial = ({ edge }: Pick<CoinProps, "edge">) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const CoinInstance = ({
|
||||
export const BaseCoin = ({
|
||||
orientation,
|
||||
size,
|
||||
thickness,
|
||||
color,
|
||||
obverse,
|
||||
reverse,
|
||||
@@ -55,15 +56,7 @@ export const CoinInstance = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<cylinderGeometry
|
||||
attach="geometry"
|
||||
args={[
|
||||
(size ?? 16) / 16,
|
||||
(size ?? 16) / 16,
|
||||
(thickness ?? 2) / 16,
|
||||
sides,
|
||||
]}
|
||||
/>
|
||||
<cylinderGeometry attach="geometry" args={[1, 1, 1, sides]} />
|
||||
|
||||
{edge?.picture ? (
|
||||
<SafeEdgeMaterial edge={edge} />
|
||||
@@ -92,3 +85,28 @@ export const CoinInstance = ({
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const CoinInstance = (coin: CoinProps) => {
|
||||
if (
|
||||
coin.value?.numeric_value === 0.2 &&
|
||||
coin.value?.currency?.name === "Euro"
|
||||
) {
|
||||
return <EuroCents20 {...coin} />;
|
||||
}
|
||||
|
||||
if (
|
||||
coin.value?.currency?.name === "Dollar" &&
|
||||
coin.issuer?.code === "bahamas"
|
||||
) {
|
||||
switch (coin.value?.numeric_value) {
|
||||
case 0.15:
|
||||
return <BahamasCents15 {...coin} />;
|
||||
case 0.1:
|
||||
return <BahamasCents10 {...coin} />;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return <BaseCoin {...coin} />;
|
||||
};
|
||||
|
||||
@@ -33,8 +33,9 @@ export const CoinInstances = (coinProps: CoinProps) => {
|
||||
Math.PI * (Math.random() * 2 - 1),
|
||||
Math.PI * (Math.random() * 2 - 1),
|
||||
);
|
||||
at(index).scaleOverride([size / 16, thickness / 16, size / 16]);
|
||||
});
|
||||
}, [at, count]);
|
||||
}, [at, count, size, thickness]);
|
||||
|
||||
return (
|
||||
<instancedMesh ref={ref} args={[undefined, undefined, count]}>
|
||||
|
||||
@@ -27,7 +27,7 @@ const SpinninType = ({
|
||||
}
|
||||
|
||||
if (category === "banknotes") {
|
||||
ref.current.rotation.y += delta;
|
||||
ref.current.rotation.x += delta;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -44,7 +44,18 @@ const SpinninType = ({
|
||||
|
||||
return (
|
||||
<mesh {...props}>
|
||||
<mesh ref={ref}>
|
||||
<mesh
|
||||
ref={ref}
|
||||
{...(category !== "banknotes"
|
||||
? {
|
||||
scale: [
|
||||
(typeProps.size ?? 16) / 16,
|
||||
(typeProps.thickness ?? 2) / 16,
|
||||
(typeProps.size ?? 16) / 16,
|
||||
],
|
||||
}
|
||||
: { rotation: [Math.PI / 2, Math.PI, Math.PI / 2] })}
|
||||
>
|
||||
<Instance {...typeProps} />
|
||||
</mesh>
|
||||
</mesh>
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
import type { NextApiHandler } from "next";
|
||||
import { readFile, readdir } from "fs/promises";
|
||||
import path from "path";
|
||||
import { spawn } from "child_process";
|
||||
|
||||
const asyncSpawn = (command: string, args: string[]) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const child = spawn(command, args, {
|
||||
stdio: "inherit",
|
||||
});
|
||||
|
||||
child.on("error", reject);
|
||||
child.stdout?.on("data", process.stdout.write);
|
||||
child.stderr?.on("data", process.stderr.write);
|
||||
child.on("close", (code) => {
|
||||
if (code === 0) {
|
||||
resolve(code);
|
||||
} else {
|
||||
reject(code);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const getCoinPath = async (id: string) => {
|
||||
const countries = await readdir("./public/coins");
|
||||
for (const country of countries) {
|
||||
const coins = await readdir(path.join("./public/coins", country));
|
||||
for (const coin of coins) {
|
||||
if (coin === id + ".json") {
|
||||
return path.join("./public/coins", country, coin);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const getPreviousResult = async (id: string) => {
|
||||
try {
|
||||
return await readFile(path.join("./public/photos/joined", id + ".jpg"));
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
const generateImage = async (id: string) => {
|
||||
const previousResult = await getPreviousResult(id);
|
||||
|
||||
if (previousResult) {
|
||||
return previousResult;
|
||||
}
|
||||
|
||||
const coinPath = await getCoinPath(id);
|
||||
|
||||
if (!coinPath) {
|
||||
return;
|
||||
}
|
||||
|
||||
const coin = JSON.parse(await readFile(coinPath, { encoding: "utf-8" }));
|
||||
|
||||
await asyncSpawn("convert", [
|
||||
path.join("./public", coin.obverse.picture),
|
||||
"-resize",
|
||||
"256x256!",
|
||||
path.join("./public/photos/joined", id + "-obverse.jpg"),
|
||||
]);
|
||||
|
||||
await asyncSpawn("convert", [
|
||||
path.join("./public", coin.reverse.picture),
|
||||
"-resize",
|
||||
"256x256!",
|
||||
path.join("./public/photos/joined", id + "-reverse.jpg"),
|
||||
]);
|
||||
|
||||
if (coin.edge?.picture) {
|
||||
await asyncSpawn("convert", [
|
||||
path.join("./public", coin.edge.picture),
|
||||
"-resize",
|
||||
"512x64!",
|
||||
path.join("./public/photos/joined", id + "-edge.jpg"),
|
||||
]);
|
||||
} else {
|
||||
await asyncSpawn("convert", [
|
||||
"-size",
|
||||
"512x64",
|
||||
"xc:" + coin.color,
|
||||
path.join("./public/photos/joined", id + "-edge.jpg"),
|
||||
]);
|
||||
}
|
||||
|
||||
await asyncSpawn("convert", [
|
||||
path.join("./public/photos/joined", id + "-obverse.jpg"),
|
||||
path.join("./public/photos/joined", id + "-reverse.jpg"),
|
||||
"+append",
|
||||
path.join("./public/photos/joined", id + ".jpg"),
|
||||
]);
|
||||
|
||||
for (let index = 0; index < 4; index++) {
|
||||
await asyncSpawn("convert", [
|
||||
path.join("./public/photos/joined", id + ".jpg"),
|
||||
path.join("./public/photos/joined", id + "-edge.jpg"),
|
||||
"-append",
|
||||
path.join("./public/photos/joined", id + ".jpg"),
|
||||
]);
|
||||
}
|
||||
|
||||
return await readFile(path.join("./public/photos/joined", id + ".jpg"));
|
||||
};
|
||||
|
||||
const handler: NextApiHandler = async ({ query }, res) => {
|
||||
if (typeof query.id !== "string") {
|
||||
res.status(400).json({
|
||||
message: `Invalid id ${JSON.stringify(query.id)}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const image = await generateImage(query.id);
|
||||
|
||||
if (!image) {
|
||||
res.status(404).json({
|
||||
message: `Coin ${JSON.stringify(query.id)} not found`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
res.setHeader("Content-Type", "image/jpg");
|
||||
res.status(200).send(image);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
res.status(500).json({
|
||||
message: `Something went wrong`,
|
||||
error: String(error),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export default handler;
|
||||
Reference in New Issue
Block a user