|
|
@ -1,21 +1,39 @@ |
|
|
|
import axios from "axios"; |
|
|
|
import * as R from "ramda"; |
|
|
|
import * as THREE from "three"; |
|
|
|
import { GLTFLoader } from "three/addons/loaders/GLTFLoader"; |
|
|
|
import * as SkeletonUtils from "three/addons/utils/SkeletonUtils"; |
|
|
|
import * as TWEEN from "@tweenjs/tween.js"; |
|
|
|
import { AppString } from "@/constants/app"; |
|
|
|
import { accountsDB, db } from "@/db"; |
|
|
|
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings"; |
|
|
|
import { accessToken } from "@/libs/crypto"; |
|
|
|
|
|
|
|
const ANIMATION_DURATION_SECS = 10; |
|
|
|
const BASE32 = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"; |
|
|
|
const ENDORSER_ENTITY_PREFIX = "https://endorser.ch/entity/"; |
|
|
|
|
|
|
|
export async function loadLandmarks(vue, world, scene, loop) { |
|
|
|
const endorserApiServer = AppString.DEFAULT_ENDORSER_API_SERVER; |
|
|
|
try { |
|
|
|
await db.open(); |
|
|
|
const settings = await db.settings.get(MASTER_SETTINGS_KEY); |
|
|
|
const activeDid = settings?.activeDid || ""; |
|
|
|
await accountsDB.open(); |
|
|
|
const accounts = await accountsDB.accounts.toArray(); |
|
|
|
const account = R.find((acc) => acc.did === activeDid, accounts); |
|
|
|
const identity = JSON.parse(account?.identity || "undefined"); |
|
|
|
const token = await accessToken(identity); |
|
|
|
|
|
|
|
const url = |
|
|
|
endorserApiServer + "/api/v2/report/claims?claimType=GiveAction"; |
|
|
|
const headers = { "Content-Type": "application/json" }; |
|
|
|
const headers = { |
|
|
|
"Content-Type": "application/json", |
|
|
|
Authorization: "Bearer " + token, |
|
|
|
}; |
|
|
|
const resp = await axios.get(url, { headers: headers }); |
|
|
|
if (resp.status === 200) { |
|
|
|
console.log("resp.data.data", resp.data.data); |
|
|
|
const minDate = resp.data.data[resp.data.data.length - 1].issuedAt; |
|
|
|
const maxDate = resp.data.data[0].issuedAt; |
|
|
|
const minTimeMillis = new Date(minDate).getTime(); |
|
|
@ -33,24 +51,27 @@ export async function loadLandmarks(vue, world, scene, loop) { |
|
|
|
//const modelLoc = "/models/coreopsis-flower.glb", // 3 flowers
|
|
|
|
// modScale = 2;
|
|
|
|
//const modelLoc = "/models/a_bush/scene.gltf", // purple leaves
|
|
|
|
// modScale = 15,
|
|
|
|
// modScale = 15;
|
|
|
|
|
|
|
|
// calculate positions for each claim, especially because some are random
|
|
|
|
const locations = resp.data.data.map((claim) => |
|
|
|
locForGive(claim, world.PLATFORM_SIZE, world.PLATFORM_EDGE_FOR_UNKNOWNS) |
|
|
|
); |
|
|
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
|
|
const parentWorld = world; |
|
|
|
loader.load( |
|
|
|
modelLoc, |
|
|
|
function (gltf) { |
|
|
|
gltf.scene.scale.set(0, 0, 0); |
|
|
|
for (const claim of resp.data.data) { |
|
|
|
for (let i = 0; i < resp.data.data.length; i++) { |
|
|
|
// claim is a GiveServerRecord (see endorserServer.ts)
|
|
|
|
const claim = resp.data.data[i]; |
|
|
|
const newPlant = SkeletonUtils.clone(gltf.scene); |
|
|
|
const randomness = claim.id.substring(10); |
|
|
|
const x = |
|
|
|
(100 * BASE32.indexOf(randomness.substring(0, 1))) / 32 - 50; |
|
|
|
const z = |
|
|
|
(100 * BASE32.indexOf(randomness.substring(8, 9))) / 32 - 50; |
|
|
|
newPlant.position.set(x, 0, z); |
|
|
|
|
|
|
|
parentWorld.scene.add(newPlant); |
|
|
|
|
|
|
|
const loc = locations[i]; |
|
|
|
newPlant.position.set(loc.x, 0, loc.z); |
|
|
|
|
|
|
|
world.scene.add(newPlant); |
|
|
|
const timeDelayMillis = |
|
|
|
fakeRealRatio * |
|
|
|
(new Date(claim.issuedAt).getTime() - minTimeMillis); |
|
|
@ -58,7 +79,7 @@ export async function loadLandmarks(vue, world, scene, loop) { |
|
|
|
.delay(timeDelayMillis) |
|
|
|
.to({ x: modScale, y: modScale, z: modScale }, 5000) |
|
|
|
.start(); |
|
|
|
parentWorld.bushes = [...parentWorld.bushes, newPlant]; |
|
|
|
world.bushes = [...world.bushes, newPlant]; |
|
|
|
} |
|
|
|
}, |
|
|
|
undefined, |
|
|
@ -68,16 +89,14 @@ export async function loadLandmarks(vue, world, scene, loop) { |
|
|
|
); |
|
|
|
|
|
|
|
// calculate when lights shine on appearing claim area
|
|
|
|
for (const claim of resp.data.data) { |
|
|
|
for (let i = 0; i < resp.data.data.length; i++) { |
|
|
|
// claim is a GiveServerRecord (see endorserServer.ts)
|
|
|
|
const claim = resp.data.data[i]; |
|
|
|
|
|
|
|
// compute location for this claim
|
|
|
|
const randomness = claim.id.substring(10); |
|
|
|
const x = (100 * BASE32.indexOf(randomness.substring(0, 1))) / 32 - 50; |
|
|
|
const z = (100 * BASE32.indexOf(randomness.substring(8, 9))) / 32 - 50; |
|
|
|
const loc = locations[i]; |
|
|
|
const light = createLight(); |
|
|
|
light.position.set(x, 20, z); |
|
|
|
light.target.position.set(x, 0, z); |
|
|
|
light.position.set(loc.x, 20, loc.z); |
|
|
|
light.target.position.set(loc.x, 0, loc.z); |
|
|
|
loop.updatables.push(light); |
|
|
|
scene.add(light); |
|
|
|
scene.add(light.target); |
|
|
@ -119,6 +138,86 @@ export async function loadLandmarks(vue, world, scene, loop) { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* |
|
|
|
* @param giveClaim |
|
|
|
* @returns {x:float, z:float} where -50 <= x & z < 50 |
|
|
|
*/ |
|
|
|
function locForGive(giveClaim, platformWidth, borderWidth) { |
|
|
|
let loc; |
|
|
|
if (giveClaim?.claim?.recipient?.identifier) { |
|
|
|
// this is directly to a person
|
|
|
|
loc = locForEthrDid(giveClaim.claim.recipient.identifier); |
|
|
|
loc = { x: loc.x - platformWidth / 2, z: loc.z - platformWidth / 2 }; |
|
|
|
} else if (giveClaim?.object?.isPartOf?.identifier) { |
|
|
|
// this is probably to a project
|
|
|
|
const objId = giveClaim.object.isPartOf.identifier; |
|
|
|
if (objId.startsWith(ENDORSER_ENTITY_PREFIX)) { |
|
|
|
loc = locForUlid(objId.substring(ENDORSER_ENTITY_PREFIX.length)); |
|
|
|
loc = { x: loc.x - platformWidth / 2, z: loc.z - platformWidth / 2 }; |
|
|
|
} |
|
|
|
} |
|
|
|
if (!loc) { |
|
|
|
// it must be outside our known addresses so let's put it somewhere random
|
|
|
|
const leftSide = Math.random() < 0.5; |
|
|
|
loc = { |
|
|
|
x: leftSide |
|
|
|
? -platformWidth / 2 - borderWidth / 2 |
|
|
|
: platformWidth / 2 + borderWidth / 2, |
|
|
|
z: Math.random() * platformWidth - platformWidth / 2, |
|
|
|
}; |
|
|
|
} |
|
|
|
return loc; |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Generate a deterministic x & z location based on the randomness in a ULID. |
|
|
|
* |
|
|
|
* We'll use the first 20 bits for the x coordinate and next 20 for the z. |
|
|
|
* That's a bit over a trillion locations... should be enough for pretty |
|
|
|
* unique locations for this fun kind of application within a network |
|
|
|
* -- though they're not guaranteed unique without the full ID. |
|
|
|
* (We're leaving 40 bits for other properties.) |
|
|
|
* |
|
|
|
* @param ulid |
|
|
|
* @returns {x: float, z: float} where 0 <= x & z < 100 |
|
|
|
*/ |
|
|
|
function locForUlid(ulid) { |
|
|
|
// The random parts of a ULID come after the first 10 characters.
|
|
|
|
const randomness = ulid.substring(10); |
|
|
|
|
|
|
|
// That leaves 16 characters of randomness, or 80 bits.
|
|
|
|
|
|
|
|
// We're currently only using 32 possible x and z values
|
|
|
|
// because the display is pretty low-fidelity at this point.
|
|
|
|
|
|
|
|
// Similar code is below.
|
|
|
|
const x = (100 * BASE32.indexOf(randomness.substring(0, 1))) / 32; |
|
|
|
const z = (100 * BASE32.indexOf(randomness.substring(4, 5))) / 32; |
|
|
|
return { x, z }; |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* See locForUlid |
|
|
|
* @param did |
|
|
|
* @returns {x: float, z: float} where 0 <= x & z < 100 |
|
|
|
*/ |
|
|
|
function locForEthrDid(did) { |
|
|
|
// "did:ethr:0x..."
|
|
|
|
if (did.length < 51) { |
|
|
|
return { x: 0, z: 0 }; |
|
|
|
} else { |
|
|
|
const randomness = did.substring(11); |
|
|
|
// We'll take the first 4 bits for 16 possible x & z values.
|
|
|
|
const xOff = parseInt(Number("0x" + randomness.substring(0, 1)), 10); |
|
|
|
const x = (xOff * 100) / 16; |
|
|
|
// ... and since we're reserving 20 bits total for x, start with character 5.
|
|
|
|
const zOff = parseInt(Number("0x" + randomness.substring(5, 6)), 10); |
|
|
|
const z = (zOff * 100) / 16; |
|
|
|
return { x, z }; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
function createLight() { |
|
|
|
const light = new THREE.SpotLight(0xffffff, 0, 0, Math.PI / 8, 0.5, 0); |
|
|
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
|
|