Add custom coin geometries
This commit is contained in:
+2
-1
@@ -1 +1,2 @@
|
||||
node_modules
|
||||
node_modules
|
||||
public/models
|
||||
@@ -103,5 +103,5 @@
|
||||
"name": "The Franklin Mint"
|
||||
}
|
||||
],
|
||||
"color": "rgb(70.34482758620689,153.8793103448276,184.6551724137931)"
|
||||
"color": "rgb(184,184,184)"
|
||||
}
|
||||
|
||||
@@ -87,14 +87,6 @@
|
||||
"size": 23.5,
|
||||
"thickness": 1.72,
|
||||
"orientation": "medal",
|
||||
"edge": {
|
||||
"description": "Plain",
|
||||
"picture": "/photos/bahamas/627d8a8a060cb7.98306122-original.jpg",
|
||||
"thumbnail": "https://en.numista.com/catalogue/photos/bahamas/627d8a8a060cb7.98306122-180.jpg",
|
||||
"picture_copyright": "Harryg",
|
||||
"picture_license_name": "CC BY",
|
||||
"picture_license_url": "https://creativecommons.org/licenses/by/4.0/deed.en"
|
||||
},
|
||||
"mints": [
|
||||
{
|
||||
"id": "16",
|
||||
@@ -105,5 +97,5 @@
|
||||
"name": "The Franklin Mint"
|
||||
}
|
||||
],
|
||||
"color": "rgb(157,143,72)"
|
||||
"color": "rgb(180,180,160)"
|
||||
}
|
||||
|
||||
@@ -87,12 +87,6 @@
|
||||
"size": 23,
|
||||
"thickness": 1.4,
|
||||
"orientation": "coin",
|
||||
"edge": {
|
||||
"description": "Plain",
|
||||
"picture": "/photos/mexique/4308-original.jpg",
|
||||
"thumbnail": "https://en.numista.com/catalogue/photos/mexique/4308-180.jpg",
|
||||
"picture_copyright": "G-REMXD"
|
||||
},
|
||||
"mints": [
|
||||
{
|
||||
"id": "29",
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -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