goals:
separate models to 4 of to 16- lazy loading
mona gibi hepsi t pose yüklenmeli
4 tane emote atılmalı, ve material düşmesi gibi arasından seçilmeli
transparent background
no mat, no anim, no mat anim, hepsi test edildi,
sıralama değiştiridl, ilk 3-4 çıkıyor, modelden bağımsız, sonra çıkmıyor
https://raw.githubusercontent.com/decentralize-dfw/3dwebhome/main/C2W-OPT-rotated-NOanim-12-v6.glb https://raw.githubusercontent.com/decentralize-dfw/3dwebhome/main/C2W-OPT-rotated-NOanim-16-v7.glb
https://raw.githubusercontent.com/decentralize-dfw/3dwebhome/main/C2W-OPT-rotated-NOanim-3-v3.glb
https://raw.githubusercontent.com/decentralize-dfw/3dwebhome/main/C2W-OPT-rotated-NOanim-6-v4.glb
https://raw.githubusercontent.com/decentralize-dfw/3dwebhome/main/C2W-OPT-rotated-NOanim-9-v5.glb
HYPERFY ANALYSİS
Let me analyze these two code files:
1. First file (paste.txt) appears to be a JavaScript library called ThreeMeshUI - a UI library built on top of Three.js for creating 3D user interfaces. Key observations:
- It provides components for creating text, blocks and other UI elements in 3D space
- Uses Three.js for rendering
- Has features for:
- Text rendering with custom fonts
- Layout system (flexbox-like)
- Styling (colors, borders, padding etc.)
- Event handling
- Components like Block, Text, Inline elements
- Seems to be focused on creating HUD-like interfaces in 3D environments
2. Second file (paste-2.txt) is a WebAssembly (Wasm) build of PhysX - NVIDIA's physics engine. Key points:
- It's a compiled C++ physics engine exposed to JavaScript through WebAssembly
- Contains bindings and glue code to interact with the PhysX physics engine from JavaScript
- Handles memory management between JS and Wasm
- Provides physics simulation capabilities like:
- Rigid body dynamics
- Collisions
Continuing the analysis:
PhysX (paste-2.txt):
- Includes extensive error handling and type checking between JS and C++
- Has an Emscripten-generated binding layer for calling PhysX functions from JavaScript
- Features include:
- Memory management utilities
- Runtime type information (RTTI) support
- Exception handling across the JS-Wasm boundary
- Dynamic linking capabilities
- Debugging and logging facilities
ThreeMeshUI (paste.txt):
- Has a component-based architecture similar to web UI frameworks
- Key components include:
- Block: Basic container element
- Text: For rendering text content
- InlineBlock: For inline-level containers
- Various style properties like:
- Margins and padding
- Borders and border radius
- Background colors and images
- Text alignment and formatting
- Font properties (size, weight, family etc)
- Uses a flexbox-inspired layout system for positioning elements
- Handles text rendering with:
- Font atlas generation
- Glyph positioning
- Text wrapping
- Unicode support
- Kerning
Yes! Let me highlight the material-related code from both libraries:
ThreeMeshUI has several material-related components:
1. Font Material Handling:
```javascript
class MSSDFBasicMaterial extends THREE.ShaderMaterial {
static get mediation() {
return MaterialProperties.mediation;
}
constructor(options = {}) {
super({
uniforms: {
glyphMap: { value: null },
diffuse: { value: null },
opacity: { value: 1 },
unitRange: { value: new THREE.Vector2(0, 0) },
alphaTest: { value: 0.02 }
},
transparent: true,
clipping: true,
vertexShader: vertexShader,
fragmentShader: fragmentShader,
extensions: {
derivatives: true
}
});
}
// Material property setters
set color(v) { this.uniforms.diffuse.value = v; }
get color() { return this.uniforms.diffuse.value; }
set opacity(v) { this.uniforms.opacity.value = v; }
get opacity() { return this.uniforms.opacity.value; }
}
```
2. Background Material Properties:
```javascript
// Background material properties
const BackgroundMaterial = {
clippingPlanes: { m: "clippingPlanes" },
backgroundAlphaTest: { m: "alphaTest", t: setAlphaTest },
backgroundSide: { m: "side" },
backgroundImage: { m: "map" },
backgroundColor: { m: "color" },
backgroundOpacity: { m: "opacity" },
backgroundSize: {
m: "computedBackgroundSize",
t: function(material, prop, value) {
setBackgroundSize(material, value);
}
}
};
```
3. Border Rendering:
```javascript
// Border material properties
const BorderMaterial = {
borderWidth: { m: "borderWidth", t: setUniform },
borderColor: { m: "borderColor", t: setUniform },
borderRadius: {
m: "borderRadius",
t: setUniform
},
borderOpacity: { m: "borderOpacity", t: setUniform }
};
```
4. Custom Shader Chunks for Materials:
```javascript
const ShaderChunks = {
msdfAlphaglyphVertexGlsl: `
varying vec2 vUvG;
void main() {
vUvG = uvG;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
msdfAlphaglyphFragmentGlsl: `
uniform sampler2D glyphMap;
uniform vec2 unitRange;
varying vec2 vUvG;
float median(float r, float g, float b) {
return max(min(r, g), min(max(r, g), b));
}
`
};
```
Key Material Features:
- Support for MSDF (Multi-channel signed distance field) text rendering
- Background materials with image mapping
- Border rendering with customizable properties
- Alpha testing and transparency
- Custom shader implementations
- Uniform management for material properties
- Material property mediation system
The PhysX code also includes some material-related physics properties:
```javascript
// Physics material properties
const PhysicsMaterialDesc = {
staticFriction: 0.5,
dynamicFriction: 0.5,
restitution: 0.6
};
// Material binding to physics engine
function createPhysicsMaterial(px) {
return px.createMaterial(
this.staticFriction,
this.dynamicFriction,
this.restitution
);
}
```
The materials in ThreeMeshUI focus on visual rendering while PhysX materials handle physics properties like friction and restitution. ThreeMeshUI has more extensive material handling for UI purposes with custom shaders and properties for text rendering, backgrounds, and borders.
please scroll down to explore designs.
<!DOCTYPE html>
<html lang="en" oncontextmenu="return false;" onselectstart="return false" ondragstart="return false">
<head>
<meta charset="UTF-8">
<title>Digital Forgery Work 3D Viewer - Sculptures</title>
<!-- SEO and Open Graph Tags -->
<meta name="description" content="Digital Forgery Work 3D Viewer - View exclusive 3D sculpture designs.">
<meta name="keywords" content="digital forgery, 3D design, digital art, NFT, sculpture">
<meta property="og:title" content="Digital Forgery Work 3D Viewer - Sculptures">
<meta property="og:type" content="website">
<meta property="og:description" content="Explore and interact with exclusive 3D digital sculptures.">
<style>
html, body {
margin: 0;
padding: 0;
scrollbar-width: none !important;
-ms-overflow-style: none !important;
overflow: -moz-scrollbars-none;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
background: transparent;
}
html::-webkit-scrollbar,
body::-webkit-scrollbar {
width: 0 !important;
height: 0 !important;
display: none !important;
background: transparent !important;
}
body {
height: 1000vh;
overflow-y: scroll;
overflow-x: hidden;
-webkit-overflow-scrolling: touch;
pointer-events: auto;
}
* {
scrollbar-width: none !important;
-ms-overflow-style: none !important;
}
*::-webkit-scrollbar {
display: none !important;
}
#canvas-container {
width: 1590px;
height: 830px;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
overflow: hidden;
background: transparent;
}
canvas {
display: block;
width: 100%;
height: 100%;
background: transparent;
}
.object-info {
position: fixed;
right: 50px;
top: 50%;
transform: translateY(-50%);
opacity: 0;
text-align: right;
pointer-events: none;
z-index: 1000;
padding: 10px;
}
.object-name {
font-family: helvetica;
font-weight: bold;
font-size: 25px;
letter-spacing: -0.25px;
color: black;
margin-bottom: 7px;
white-space: nowrap;
}
.object-type {
font-family: helvetica;
font-weight: 500;
font-size: 15px;
letter-spacing: -0.05px;
color: black;
margin-bottom: 7px;
white-space: nowrap;
}
.object-colorways {
font-family: helvetica;
font-weight: 500;
font-style: italic;
font-size: 12px;
letter-spacing: -0.15px;
color: #595959;
margin-bottom: 7px;
white-space: nowrap;
}
.object-link {
font-family: helvetica;
font-weight: 500;
font-style: italic;
font-size: 12px;
letter-spacing: -0.15px;
color: rgb(9, 7, 102);
text-decoration: none;
cursor: pointer;
white-space: nowrap;
pointer-events: none;
}
.object-link:hover {
text-decoration: underline;
}
.market-button {
font-family: helvetica;
font-weight: 500;
font-style: italic;
font-size: 11px;
letter-spacing: -0.15px;
color: black;
padding: 7px 14px;
background-color: rgba(255, 255, 255, 0.7);
border: 1px solid #000000;
border-radius: 10px;
cursor: pointer;
transition: all 0.2s ease;
pointer-events: auto;
margin-top: 10px;
}
.market-button:hover {
background-color: rgba(0, 0, 0, 0.6);
color: white;
border-color: transparent;
}
#wireframe-toggle {
position: fixed;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
padding: 7px 7px;
background-color: rgba(0, 0, 0, 0.5);
color: rgba(255, 255, 255, 0.5);
border: 1px solid #808080;
border-radius: 5px;
cursor: pointer;
font-family: helvetica;
font-size: 12px;
font-style: italic;
letter-spacing: -0.15px;
z-index: 1000;
transition: all 0.3s;
}
#wireframe-toggle:hover {
background-color: rgba(0, 0, 0, 0.6) !important;
color: white !important;
border-color: transparent !important;
}
#wireframe-toggle.active {
background-color: transparent;
color: black;
}
#wireframe-toggle.active:hover {
background-color: rgba(0, 0, 0, 0.6) !important;
color: white !important;
border-color: transparent !important;
}
</style>
</head>
<body>
<div id="canvas-container"></div>
<button id="wireframe-toggle">Material</button>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/loaders/GLTFLoader.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/loaders/DRACOLoader.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/loaders/RGBELoader.js"></script>
<script>
// Security and protection scripts with IIFE for proper scoping
(function() {
const preventDefault = (e) => e.preventDefault();
document.addEventListener('contextmenu', preventDefault);
document.addEventListener('keydown', function(e) {
if ((e.ctrlKey && (e.key === 'u' || e.key === 'U' || e.key === 'i' || e.key === 'I' || e.key === 's' || e.key === 'S')) ||
e.key === 'F12' ||
(e.ctrlKey && e.shiftKey && (e.key === 'i' || e.key === 'I' || e.key === 'j' || e.key === 'J' || e.key === 'c' || e.key === 'C'))) {
preventDefault(e);
}
});
['keypress', 'mousedown', 'keydown'].forEach(event => {
document.addEventListener(event, (e) => {
if (e.keyCode === 123) preventDefault(e);
});
});
['selectstart', 'dragstart'].forEach(event => {
window.addEventListener(event, preventDefault);
});
})();
// Main application with proper scoping
(function initializeViewer() {
// Helper function to create DOM elements with properties
function createElement(tag, className, text) {
const element = document.createElement(tag);
if (className) element.className = className;
if (text) element.textContent = text;
return element;
}
// Object data with proper organization
const objectData = {
"1": {
"name": "Osiris Rake",
"type": "sculpture1",
"colorways": "OG, BLCK, GRY, RED",
"link": "https://opensea.io/collection/dfw-design?search[query]=osiris"
},
"2": {
"name": "Midas Spade",
"type": "sculpture2",
"colorways": "OG, DARK, LIGHT, CANDLE",
"link": "https://opensea.io/collection/dfw-design?search[query]=rake"
},
"3": {
"name": "Angry Napkin",
"type": "sculpture3",
"colorways": "OG, BLCK, WHT, RED",
"link": "https://opensea.io/collection/dfw-design?search[query]=angry%20napkin"
},
"4": {
"name": "W6-O1-M6_(chemical_structure)",
"type": "sculpture4",
"colorways": "OG, BLCK, GRY, RED",
"link": "https://opensea.io/collection/dfw-design?search[query]=w6-o1-m6"
},
"5": {
"name": "Statue of Hangover",
"type": "sculpture5",
"colorways": "SOHiii, SOHii, SOHi",
"link": "https://opensea.io/collection/dfw-design?search[query]=statue"
},
"6": {
"name": "Praified",
"type": "sculpture6",
"colorways": "OG, BLCK, WHT, RED",
"link": "https://opensea.io/collection/dfw-design?search[query]=praified"
},
"7": {
"name": "Sanctified",
"type": "sculpture7",
"colorways": "OG, BLCK, WHT, RED",
"link": "https://opensea.io/collection/dfw-design?search[query]=sanctified"
},
"8": {
"name": "me-tree-x",
"type": "sculpture8",
"colorways": "OG, BLCK, WHT, RED",
"link": "https://opensea.io/collection/dfw-design?search[query]=me-tree-x"
},
"9": {
"name": "moleculer-data-plant",
"type": "sculpture9",
"colorways": "OG, BLCK, WHT, RED",
"link": "https://opensea.io/collection/dfw-design?search[query]=moleculer-data-plant"
},
"10": {
"name": "Neo Klein Cube",
"type": "sculpture10",
"colorways": "OG, BLCK, GRY, RED",
"link": "https://opensea.io/collection/dfw-design?search[query]=neo%20klein"
},
"11": {
"name": "Chromie Bridge Aural Section",
"type": "sculpture11",
"colorways": "OG, ALIEN, GRY, RED",
"link": "https://opensea.io/collection/dfw-design?search[query]=chromie%20bridge&search[stringTraits][0][name]=Type&search[stringTraits][0][values][0]=Sculpture"
},
"12": {
"name": "CROOKEDASWEFUNASYU",
"type": "sculpture12",
"colorways": "OG, BLCK, WHT, RED",
"link": "https://opensea.io/collection/dfw-design?search[query]=crook&search[stringTraits][0][name]=Type&search[stringTraits][0][values][0]=Sculpture"
},
"13": {
"name": "LEGEND",
"type": "sculpture13",
"colorways": "OG, BLCK, WHT, RED",
"link": "https://opensea.io/collection/dfw-design?search[query]=legend&search[stringTraits][0][name]=Type&search[stringTraits][0][values][0]=Sculpture"
},
"14": {
"name": "2002",
"type": "sculpture14",
"colorways": "OG, BLCK, TEA, RED",
"link": "https://opensea.io/collection/dfw-design?search[query]=2002&search[stringTraits][0][name]=Type&search[stringTraits][0][values][0]=Sculpture"
},
"15": {
"name": "0ff_with_his_head",
"type": "sculpture15",
"colorways": "OG, BLCK, WHT, RED",
"link": "https://opensea.io/collection/dfw-design?search[query]=0ff"
},
"16": {
"name": "Mystical Arcases",
"type": "sculpture16",
"colorways": "BLCK, RED, WHT, MIX",
"link": "https://opensea.io/collection/dfw-design?search[query]=mystical&search[stringTraits][0][name]=Type&search[stringTraits][0][values][0]=Sculpture"
}
};
// Configuration object
const config = {
model: {
url: 'https://raw.githubusercontent.com/decentralize-dfw/3dwebhome/main/C2W-OPT-rotated_256-v5.glb'
},
animation: {
frameCount: 31,
speedMultiplier: .5
},
camera: {
start: { x: 0.5, y: 8, z: 7 },
end: { x: 0.5, y: 104, z: 7 },
rotation: { x: -45, y: 0, z: 0 },
orthographicScale: 3
},
dimensions: {
width: 1590,
height: 830
}
};
// Debug helper
let lastLogTime = 0;
const LOG_THROTTLE = 1000;
function debugLog(message, data = {}) {
const now = Date.now();
if (now - lastLogTime > LOG_THROTTLE) {
console.log(`[DEBUG] ${message}`, data);
lastLogTime = now;
}
}
// Opacity calculation based on camera position
function calculateOpacity(objectId, cameraY) {
const objectId_num = parseInt(objectId);
// Calculate center point for each object
// Starting at 8, incrementing by 6 for each object
const objectCenter = 8 + (6 * (objectId_num - 1));
// Define opacity transition points
const zeroStart = objectCenter - 2.6; // Point where opacity begins
const oneStart = objectCenter - 2.3; // Point where opacity reaches 1%
const oneEnd = objectCenter + 2.3; // Point where opacity starts decreasing from 100%
const zeroEnd = objectCenter + 2.6; // Point where opacity reaches 0%
// Return 0 if outside the opacity range
if (cameraY <= zeroStart || cameraY >= zeroEnd) return 0;
// Calculate opacity for fade in
if (cameraY > zeroStart && cameraY <= objectCenter) {
if (cameraY <= oneStart) {
// Fade from 0% to 1%
return 0.01 * ((cameraY - zeroStart) / (oneStart - zeroStart));
} else {
// Fade from 1% to 100%
const range = objectCenter - oneStart;
const distance = cameraY - oneStart;
return Math.min(1, 0.01 + (0.99 * (distance / range)));
}
}
// Calculate opacity for fade out
if (cameraY > objectCenter && cameraY < zeroEnd) {
if (cameraY >= oneEnd) {
// Fade from 1% to 0%
return 0.01 * ((zeroEnd - cameraY) / (zeroEnd - oneEnd));
} else {
// Fade from 100% to 1%
const range = oneEnd - objectCenter;
const distance = oneEnd - cameraY;
return Math.max(0.01, 0.01 + (0.99 * (distance / range)));
}
}
return 0;
}
// Create elements
function createObjectInfoElements() {
debugLog('Creating info elements');
const existingElements = document.querySelectorAll('.object-info');
existingElements.forEach(el => el.remove());
const mainContainer = document.createElement('div');
mainContainer.id = 'info-container';
Object.entries(objectData).forEach(([objectId, objectInfo]) => {
const container = createElement('div', 'object-info', null);
container.id = `object-info-${objectId}`;
const nameElement = createElement('div', 'object-name', objectInfo.name);
const typeElement = createElement('div', 'object-type', objectInfo.type);
const colorwaysElement = createElement('div', 'object-colorways', objectInfo.colorways);
const marketButton = createElement('button', 'market-button', 'open in market');
marketButton.id = `market-button-${objectId}`;
marketButton.onclick = () => window.open(objectInfo.link, '_blank', 'noopener');
container.appendChild(nameElement);
container.appendChild(typeElement);
container.appendChild(colorwaysElement);
container.appendChild(marketButton);
mainContainer.appendChild(container);
});
document.body.appendChild(mainContainer);
}
// Update visibility
// Helper function to update object info visibility
function updateObjectInfo(objectId, opacity) {
const info = document.getElementById(`object-info-${objectId}`);
if (info) {
info.style.opacity = opacity;
const button = document.getElementById(`market-button-${objectId}`);
if (button) {
button.style.opacity = opacity;
button.style.pointerEvents = opacity > 0.01 ? 'auto' : 'none';
}
}
}
// Three.js setup
const scene = new THREE.Scene();
const aspect = config.dimensions.width / config.dimensions.height;
const camera = new THREE.OrthographicCamera(
-config.camera.orthographicScale * aspect,
config.camera.orthographicScale * aspect,
config.camera.orthographicScale,
-config.camera.orthographicScale,
0.1,
1000
);
const renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true,
premultipliedAlpha: false,
logarithmicDepthBuffer: true
});
renderer.setSize(config.dimensions.width, config.dimensions.height);
renderer.setClearColor(0x000000, 0);
renderer.physicallyCorrectLights = true;
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.2;
renderer.outputEncoding = THREE.sRGBEncoding;
renderer.setPixelRatio(window.devicePixelRatio);
document.getElementById('canvas-container').appendChild(renderer.domElement);
camera.position.set(config.camera.start.x, config.camera.start.y, config.camera.start.z);
camera.rotation.set(
THREE.MathUtils.degToRad(config.camera.rotation.x),
THREE.MathUtils.degToRad(config.camera.rotation.y),
THREE.MathUtils.degToRad(config.camera.rotation.z)
);
// Lighting setup
// Lighting setup with much higher intensities
const ambientLight = new THREE.AmbientLight(0xffffff, 1.0); // Doubled from 1.0
scene.add(ambientLight);
const mainLight = new THREE.DirectionalLight(0xffffff, 1.5); // More than doubled from 1.2
mainLight.position.set(0, 10, 10);
mainLight.castShadow = true;
scene.add(mainLight);
const frontFill = new THREE.DirectionalLight(0xffffff, 1.0); // Nearly doubled from 0.8
frontFill.position.set(0, 0, 5);
scene.add(frontFill);
const topFill = new THREE.DirectionalLight(0xffffff, 1.5); // Nearly doubled from 0.8
topFill.position.set(0, 5, 0);
scene.add(topFill);
const backFill = new THREE.DirectionalLight(0xffffff, 1.2); // Doubled from 0.6
backFill.position.set(0, 0, -5);
scene.add(backFill);
// Increased point light intensities
const pointLight1 = new THREE.PointLight(0xffffff, 1.0); // Doubled from 0.5
pointLight1.position.set(5, 5, 5);
scene.add(pointLight1);
const pointLight2 = new THREE.PointLight(0xffffff, 1.0); // Doubled from 0.5
pointLight2.position.set(-5, 5, -5);
scene.add(pointLight2);
// Add extra fill lights for better coverage
const pointLight3 = new THREE.PointLight(0xffffff, 1.0); // New light
pointLight3.position.set(0, -5, 0);
scene.add(pointLight3);
const pointLight4 = new THREE.PointLight(0xffffff, 1.0); // New light
pointLight4.position.set(0, 5, -5);
scene.add(pointLight4);
// Add wireframe state tracking
let isWireframe = true;
let currentModel = null;
// Wireframe toggle button functionality
const wireframeToggle = document.getElementById('wireframe-toggle');
function toggleWireframe() {
isWireframe = !isWireframe;
wireframeToggle.textContent = isWireframe ? 'Material' : 'Wireframe';
wireframeToggle.classList.toggle('active', isWireframe);
if (currentModel) {
currentModel.traverse((child) => {
if (child.isMesh) {
if (isWireframe) {
// Store original material
child.userData.originalMaterial = child.material;
// Create simple wireframe material
child.material = new THREE.MeshBasicMaterial({
wireframe: true,
color: 0x000000,
transparent: true,
opacity: 0.2,
side: THREE.DoubleSide
});
} else {
// Restore original material
if (child.userData.originalMaterial) {
child.material = child.userData.originalMaterial;
}
}
}
});
}
}
wireframeToggle.addEventListener('click', toggleWireframe);
// Model loading setup
const dracoLoader = new THREE.DRACOLoader();
dracoLoader.setDecoderPath('https://www.gstatic.com/draco/v1/decoders/');
const loader = new THREE.GLTFLoader();
loader.setDRACOLoader(dracoLoader);
let mixer;
// Create info elements before loading model
createObjectInfoElements();
loader.load(config.model.url, function(gltf) {
const model = gltf.scene;
currentModel = model;
model.traverse((child) => {
if (child.isMesh) {
// Enhance material settings for better visibility
child.material.transparent = true;
child.material.opacity = 1;
child.material.metalness = 0.4;
child.material.roughness = 0.2;
child.material.envMapIntensity = 3.0;
child.material.reflectivity = 1.0;
child.material.side = THREE.DoubleSide;
child.material.alphaToCoverage = true;
child.material.depthWrite = true;
// Ensure proper shadows and rendering
child.castShadow = true;
child.receiveShadow = true;
}
});
scene.add(model);
if (gltf.animations && gltf.animations.length) {
mixer = new THREE.AnimationMixer(model);
gltf.animations.forEach(clip => {
const action = mixer.clipAction(clip);
action.play();
});
}
});
// Animation and camera movement setup
let currentY = config.camera.start.y;
let targetY = currentY;
const smoothness = 0.05;
let lastCameraUpdate = 0;
const CAMERA_UPDATE_THROTTLE = 16;
function lerp(start, end, factor) {
return start + (factor * (end - start));
}
function updateCamera() {
const now = Date.now();
if (now - lastCameraUpdate < CAMERA_UPDATE_THROTTLE) return;
const scrollFraction = window.pageYOffset / (document.documentElement.scrollHeight - window.innerHeight);
const currentCameraY = config.camera.start.y + (scrollFraction * (config.camera.end.y - config.camera.start.y));
if (mixer) {
const animationTime = (scrollFraction * 31) * 0.3;
mixer.setTime(animationTime % 31);
}
targetY = currentCameraY;
currentY = lerp(currentY, targetY, 0.05);
camera.position.y = currentY;
// Update opacity for all objects
Object.keys(objectData).forEach(objectId => {
const opacity = calculateOpacity(objectId, currentY);
updateObjectInfo(objectId, opacity);
});
lastCameraUpdate = now;
}
function animate() {
requestAnimationFrame(animate);
updateCamera();
renderer.render(scene, camera);
}
// Start animation loop
animate();
// Handle window resizing
window.addEventListener('resize', function() {
const aspect = config.dimensions.width / config.dimensions.height;
// Update camera frustum
camera.left = -config.camera.orthographicScale * aspect;
camera.right = config.camera.orthographicScale * aspect;
camera.top = config.camera.orthographicScale;
camera.bottom = -config.camera.orthographicScale;
// Update camera and renderer
camera.updateProjectionMatrix();
renderer.setSize(config.dimensions.width, config.dimensions.height);
});
})();
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en" oncontextmenu="return false;" onselectstart="return false" ondragstart="return false">
<head>
<meta charset="UTF-8">
<title>Digital Forgery Work 3D Viewer - Sculptures</title>
<meta name="description" content="Digital Forgery Work 3D Viewer - View exclusive 3D sculpture designs.">
<meta name="keywords" content="digital forgery, 3D design, digital art, NFT, sculpture">
<meta property="og:title" content="Digital Forgery Work 3D Viewer - Sculptures">
<meta property="og:type" content="website">
<meta property="og:description" content="Explore and interact with exclusive 3D digital sculptures.">
<style>
html, body {
margin: 0;
padding: 0;
scrollbar-width: none !important;
-ms-overflow-style: none !important;
overflow: -moz-scrollbars-none;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
background: transparent;
}
html::-webkit-scrollbar,
body::-webkit-scrollbar {
width: 0 !important;
height: 0 !important;
display: none !important;
background: transparent !important;
}
body {
height: 1000vh;
overflow-y: scroll;
overflow-x: hidden;
-webkit-overflow-scrolling: touch;
pointer-events: auto;
}
* {
scrollbar-width: none !important;
-ms-overflow-style: none !important;
}
*::-webkit-scrollbar {
display: none !important;
}
#canvas-container {
width: 1590px;
height: 830px;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
overflow: hidden;
background: transparent;
}
canvas {
display: block;
width: 100%;
height: 100%;
background: transparent;
}
.object-info {
position: fixed;
right: 50px;
top: 50%;
transform: translateY(-50%);
opacity: 0;
text-align: right;
pointer-events: none;
z-index: 1000;
padding: 10px;
}
.object-name {
font-family: helvetica;
font-weight: bold;
font-size: 25px;
letter-spacing: -0.25px;
color: black;
margin-bottom: 7px;
white-space: nowrap;
}
.object-type {
font-family: helvetica;
font-weight: 500;
font-size: 15px;
letter-spacing: -0.05px;
color: black;
margin-bottom: 7px;
white-space: nowrap;
}
.object-colorways {
font-family: helvetica;
font-weight: 500;
font-style: italic;
font-size: 12px;
letter-spacing: -0.15px;
color: #595959;
margin-bottom: 7px;
white-space: nowrap;
}
.market-button {
font-family: helvetica;
font-weight: 500;
font-style: italic;
font-size: 11px;
letter-spacing: -0.15px;
color: black;
padding: 7px 14px;
background-color: rgba(255, 255, 255, 0.7);
border: 1px solid #000000;
border-radius: 10px;
cursor: pointer;
transition: all 0.2s ease;
pointer-events: auto;
margin-top: 10px;
opacity: 0;
}
.market-button:hover {
background-color: rgba(0, 0, 0, 0.6);
color: white;
border-color: transparent;
}
#wireframe-toggle {
position: fixed;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
padding: 7px 7px;
background-color: rgba(0, 0, 0, 0.5);
color: rgba(255, 255, 255, 0.5);
border: 1px solid #808080;
border-radius: 5px;
cursor: pointer;
font-family: helvetica;
font-size: 12px;
font-style: italic;
letter-spacing: -0.15px;
z-index: 1000;
transition: all 0.3s;
}
#wireframe-toggle:hover {
background-color: rgba(0, 0, 0, 0.6) !important;
color: white !important;
border-color: transparent !important;
}
#wireframe-toggle.active {
background-color: transparent;
color: black;
}
#wireframe-toggle.active:hover {
background-color: rgba(0, 0, 0, 0.6) !important;
color: white !important;
border-color: transparent !important;
}
#loading-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(255, 255, 255, 0.9);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 2000;
}
.loading-spinner {
width: 50px;
height: 50px;
border: 5px solid #f3f3f3;
border-top: 5px solid #3498db;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 20px;
}
.loading-text {
font-family: helvetica;
font-size: 16px;
color: #333;
}
.loading-progress {
width: 200px;
height: 4px;
background: #f3f3f3;
margin-top: 10px;
border-radius: 2px;
overflow: hidden;
}
.loading-progress-bar {
width: 0%;
height: 100%;
background: #3498db;
transition: width 0.3s ease;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
</head>
<body>
<div id="canvas-container"></div>
<button id="wireframe-toggle">Material</button>
<div id="loading-overlay">
<div class="loading-spinner"></div>
<div class="loading-text">Loading 3D Model...</div>
<div class="loading-progress">
<div class="loading-progress-bar"></div>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/loaders/GLTFLoader.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/loaders/DRACOLoader.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/loaders/RGBELoader.js"></script>
<script>
class SceneManager {
constructor(config) {
this.scene = new THREE.Scene();
this.config = config;
this.setupCamera();
this.setupRenderer();
this.setupLights();
this.setupEnvironment();
}
setupCamera() {
const aspect = this.config.dimensions.width / this.config.dimensions.height;
this.camera = new THREE.OrthographicCamera(
-this.config.camera.orthographicScale * aspect,
this.config.camera.orthographicScale * aspect,
this.config.camera.orthographicScale,
-this.config.camera.orthographicScale,
0.1,
1000
);
this.camera.position.set(
this.config.camera.start.x,
this.config.camera.start.y,
this.config.camera.start.z
);
this.camera.rotation.set(
THREE.MathUtils.degToRad(this.config.camera.rotation.x),
THREE.MathUtils.degToRad(this.config.camera.rotation.y),
THREE.MathUtils.degToRad(this.config.camera.rotation.z)
);
}
setupRenderer() {
this.renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true,
premultipliedAlpha: false,
logarithmicDepthBuffer: true,
preserveDrawingBuffer: true
});
this.renderer.setSize(this.config.dimensions.width, this.config.dimensions.height);
this.renderer.setClearColor(0x000000, 0);
this.renderer.physicallyCorrectLights = true;
this.renderer.toneMapping = THREE.ACESFilmicToneMapping;
this.renderer.toneMappingExposure = 1.2;
this.renderer.outputEncoding = THREE.sRGBEncoding;
this.renderer.shadowMap.enabled = true;
this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
document.getElementById('canvas-container').appendChild(this.renderer.domElement);
}
setupLights() {
const lights = [];
lights.push(new THREE.AmbientLight(0xffffff, 1.0));
const mainLight = new THREE.DirectionalLight(0xffffff, 1.5);
mainLight.position.set(0, 10, 10);
mainLight.castShadow = true;
mainLight.shadow.mapSize.width = 2048;
mainLight.shadow.mapSize.height = 2048;
lights.push(mainLight);
['front', 'top', 'back'].forEach((pos, i) => {
let light = new THREE.DirectionalLight(0xffffff,[1.0, 1.5, 1.2][i]);
light.position.set(
[0, 0, 0][i],
[0, 5, 0][i],
[5, 0, -5][i]
);
lights.push(light);
});
[[5, 5, 5], [-5, 5, -5],[0, -5, 0],[0, 5, -5]].forEach(coord => {
let light = new THREE.PointLight(0xffffff, 1.0);
light.position.set(...coord);
lights.push(light);
});
lights.forEach(light => this.scene.add(light));
}
setupEnvironment() {
const rgbeLoader = new THREE.RGBELoader();
rgbeLoader.load(
'https://raw.githubusercontent.com/decentralize-dfw/3dwebhome/main/cyberpunk-hdr.hdr',
(texture) => {
texture.mapping = THREE.EquirectangularReflectionMapping;
this.scene.environment = texture;
this.scene.background = texture;
this.scene.backgroundBlurriness = 0.5;
}
);
}
resize() {
const aspect = this.config.dimensions.width / this.config.dimensions.height;
this.camera.left = -this.config.camera.orthographicScale * aspect;
this.camera.right = this.config.camera.orthographicScale * aspect;
this.camera.top = this.config.camera.orthographicScale;
this.camera.bottom = -this.config.camera.orthographicScale;
this.camera.updateProjectionMatrix();
this.renderer.setSize(this.config.dimensions.width, this.config.dimensions.height);
}
}
class MaterialManager {
constructor() {
this.materialCache = new Map();
this.wireframeMaterialCache = new Map();
}
setupAdvancedMaterials(model) {
model.traverse((child) => {
if (child.isMesh) {
const originalMaterial = child.material;
this.materialCache.set(child.uuid, originalMaterial.clone());
const wireframeMaterial = new THREE.MeshPhongMaterial({
color: 0x000000,
wireframe: true,
transparent: true,
opacity: 0.65,
side: THREE.DoubleSide,
skinning: true,
morphTargets: true,
depthTest: true,
depthWrite: true
});
this.wireframeMaterialCache.set(child.uuid, wireframeMaterial);
child.material = wireframeMaterial;
this.setupAdvancedMaterialProperties(originalMaterial, child);
}
});
return { materialCache: this.materialCache, wireframeMaterialCache: this.wireframeMaterialCache };
}
setupAdvancedMaterialProperties(material, mesh) {
material.envMapIntensity = 1.5;
material.needsUpdate = true;
if (material.type === 'MeshStandardMaterial' || material.type === 'MeshPhysicalMaterial') {
material.envMapIntensity = 3.0;
material.metalness = Math.min(Math.max(material.metalness || 0.4, 0), 1);
material.roughness = Math.min(Math.max(material.roughness || 0.2, 0), 1);
material.reflectivity = material.reflectivity || 1.0;
if (material.transmission > 0) {
material.transmission = Math.min(Math.max(material.transmission, 0), 1);
material.thickness = material.thickness || 0.01;
}
}
if (material.transparent || material.alphaTest > 0) {
if (material.alphaTest > 0) {
material.transparent = true;
material.depthWrite = true;
material.alphaTest = Math.max(material.alphaTest, 0.5);
} else {
material.transparent = true;
material.depthWrite = false;
}
material.depthTest = true;
material.needsUpdate = true;
}
mesh.castShadow = true;
mesh.receiveShadow = true;
mesh.updateMatrixWorld(true);
}
}
class AnimationController {
constructor(model, animations) {
this.mixer = new THREE.AnimationMixer(model);
this.clock = new THREE.Clock();
this.animations = animations;
this.actions = new Map();
if (animations) {
animations.forEach(clip => {
const action = this.mixer.clipAction(clip);
this.actions.set(clip.name, action);
action.play();
});
}
}
update() {
if (this.mixer) {
this.mixer.update(this.clock.getDelta());
}
}
setTime(time) {
if (this.mixer) {
this.mixer.setTime(time);
}
}
}
class ModelLoader {
constructor(onProgress, onError) {
this.dracoLoader = new THREE.DRACOLoader();
this.dracoLoader.setDecoderPath('https://www.gstatic.com/draco/v1/decoders/');
this.loader = new THREE.GLTFLoader();
this.loader.setDRACOLoader(this.dracoLoader);
this.onProgress = onProgress;
this.onError = onError;
}
async loadModel(url) {
return new Promise((resolve, reject) => {
this.loader.load(
url,
(gltf) => resolve(gltf),
(xhr) => {
if (xhr.lengthComputable && this.onProgress) {
const percentComplete = (xhr.loaded / xhr.total) * 100;
this.onProgress(percentComplete);
}
},
(error) => {
if (this.onError) this.onError(error);
reject(error);
}
);
});
}
}
class UIController {
constructor() {
this.loadingOverlay = document.getElementById('loading-overlay');
this.loadingProgressBar = document.querySelector('.loading-progress-bar');
this.wireframeToggle = document.getElementById('wireframe-toggle');
this.wireframeToggle.addEventListener('click', () => {
if (this.onWireframeToggle) {
this.onWireframeToggle();
}
});
}
updateLoadingProgress(progress) {
this.loadingProgressBar.style.width = `${progress}%`;
}
showLoadingError(message) {
const loadingText = document.querySelector('.loading-text');
loadingText.classList.add('error-message');
loadingText.textContent = message;
document.querySelector('.loading-spinner').style.display = 'none';
document.querySelector('.loading-progress').style.display = 'none';
}
hideLoading() {
this.loadingOverlay.style.opacity = '0';
setTimeout(() => {
this.loadingOverlay.style.display = 'none';
}, 300);
}
setWireframeToggleCallback(callback) {
this.onWireframeToggle = callback;
}
}
class ModelViewer {
constructor(config, objectData) {
this.config = config;
this.objectData = objectData;
this.isWireframe = true;
this.sceneManager = new SceneManager(config);
this.materialManager = new MaterialManager();
this.uiController = new UIController();
this.setupModelLoader();
this.setupEventListeners();
this.animate = this.animate.bind(this);
}
setupModelLoader() {
this.modelLoader = new ModelLoader(
(progress) => this.uiController.updateLoadingProgress(progress),
(error) => {
console.error('Error loading model:', error);
this.uiController.showLoadingError('Failed to load 3D model. Please refresh the page.');
}
);
}
async init() {
try {
const gltf = await this.modelLoader.loadModel(this.config.model.url);
this.currentModel = gltf.scene;
const materials = this.materialManager.setupAdvancedMaterials(this.currentModel);
this.animationController = new AnimationController(this.currentModel, gltf.animations);
this.sceneManager.scene.add(this.currentModel);
this.uiController.hideLoading();
this.animate();
} catch (error) {
console.error('Failed to initialize:', error);
this.uiController.showLoadingError('Failed to initialize viewer. Please refresh the page.');
}
}
setupEventListeners() {
window.addEventListener('resize', () => this.sceneManager.resize());
this.uiController.setWireframeToggleCallback(() => this.toggleWireframe());
}
toggleWireframe() {
this.isWireframe = !this.isWireframe;
if (!this.currentModel) return;
this.currentModel.traverse((child) => {
if (child.isMesh) {
const newMaterial = this.isWireframe
? this.materialManager.wireframeMaterialCache.get(child.uuid)
: this.materialManager.materialCache.get(child.uuid);
if (newMaterial) {
child.material = newMaterial;
child.material.needsUpdate = true;
child.updateMatrixWorld(true);
}
}
});
}
animate() {
requestAnimationFrame(this.animate);
try {
this.updateCamera();
if (this.animationController) {
this.animationController.update();
}
this.sceneManager.renderer.render(this.sceneManager.scene, this.sceneManager.camera);
} catch (error) {
console.error('Animation error:', error);
this.uiController.showLoadingError('An error occurred during animation.');
}
}
updateCamera() {
const scrollFraction = window.pageYOffset / (document.documentElement.scrollHeight - window.innerHeight);
const currentCameraY = this.config.camera.start.y +
(scrollFraction * (this.config.camera.end.y - this.config.camera.start.y));
if (this.animationController) {
const animationTime = (scrollFraction * this.config.animation.frameCount) *
this.config.animation.speedMultiplier;
this.animationController.setTime(animationTime % this.config.animation.frameCount);
}
this.currentY = this.currentY || this.config.camera.start.y;
this.targetY = currentCameraY;
this.currentY = this.lerp(this.currentY, this.targetY, this.config.camera.smoothness || 0.05);
this.sceneManager.camera.position.y = this.currentY;
this.updateObjectVisibility(this.currentY);
}
updateObjectVisibility(cameraY) {
Object.entries(this.objectData).forEach(([objectId, objectInfo]) => {
const opacity = this.calculateOpacity(objectId, cameraY);
this.updateObjectInfo(objectId, opacity);
});
}
calculateOpacity(objectId, cameraY) {
const objectId_num = parseInt(objectId);
const objectCenter = 8 + (6 * (objectId_num - 1));
const zeroStart = objectCenter - 2.6;
const oneStart = objectCenter - 2.3;
const oneEnd = objectCenter + 2.3;
const zeroEnd = objectCenter + 2.6;
if (cameraY <= zeroStart || cameraY >= zeroEnd) return 0;
if (cameraY > zeroStart && cameraY <= objectCenter) {
if (cameraY <= oneStart) {
return 0.01 * ((cameraY - zeroStart) / (oneStart - zeroStart));
} else {
const range = objectCenter - oneStart;
const distance = cameraY - oneStart;
return Math.min(1, 0.01 + (0.99 * (distance / range)));
}
}
if (cameraY > objectCenter && cameraY < zeroEnd) {
if (cameraY >= oneEnd) {
return 0.01 * ((zeroEnd - cameraY) / (zeroEnd - oneEnd));
} else {
const range = oneEnd - objectCenter;
const distance = oneEnd - cameraY;
return Math.max(0.01, 0.01 + (0.99 * (distance / range)));
}
}
return 0;
}
updateObjectInfo(objectId, opacity) {
const info = document.getElementById(`object-info-${objectId}`);
if (info) {
info.style.opacity = opacity;
const button = document.getElementById(`market-button-${objectId}`);
if (button) {
button.style.opacity = opacity;
button.style.pointerEvents = opacity > 0.01 ? 'auto' : 'none';
}
}
}
lerp(start, end, factor) {
return start + (factor * (end - start));
}
}
const config = {
model: {
url: 'https://raw.githubusercontent.com/decentralize-dfw/3dwebhome/main/C2W-test-deletedfirst4-128px-v4.glb'
},
animation: {
frameCount: 31,
speedMultiplier: 0.5
},
camera: {
start: { x: -0, y: 15, z: 20 },
end: { x: 0, y: 104, z: 20 },
rotation: { x: -60, y: 0, z: 0 },
orthographicScale: 4,
smoothness: 0.05
},
dimensions: {
width: 1590,
height: 830
},
debug: {
enabled: false,
showStats: false
}
};
const objectData = {
"1": {
"name": "Osiris Rake",
"type": "sculpture1",
"colorways": "OG, BLCK, GRY, RED",
"link": "https://opensea.io/collection/dfw-design?search[query]=osiris"
},
// Add more objects here as needed
};
document.addEventListener('DOMContentLoaded', async () => {
try {
const viewer = new ModelViewer(config, objectData);
await viewer.init().catch(error => {
console.error('Failed to initialize viewer:', error);
const loadingText = document.querySelector('.loading-text');
loadingText.textContent = 'Failed to load the viewer. Please refresh the page.';
loadingText.style.color = '#e74c3c';
});
if (config.debug.enabled) {
window.viewer = viewer;
if (config.debug.showStats) {
const Stats = require('stats.js');
const stats = new Stats();
document.body.appendChild(stats.dom);
function animate() {
stats.begin();
stats.end();
requestAnimationFrame(animate);
}
animate();
}
}
} catch (error) {
console.error('Critical error during viewer initialization:', error);
const loadingText = document.querySelector('.loading-text');
loadingText.textContent = 'A critical error occurred. Please try again later.';
loadingText.style.color = '#e74c3c';
}
});
window.addEventListener('unload', () => {
if (window.viewer) {
viewer.sceneManager.scene.traverse((object) => {
if (object.geometry) object.geometry.dispose();
if (object.material) {
Object.entries(object.material).forEach(([key, value]) => {
if (value && value.dispose instanceof Function) value.dispose();
});
object.material.dispose();
}
});
viewer.sceneManager.renderer.dispose();
}
});
</script>
</body>
</html>```