feat: project setup for week 3

This commit is contained in:
Divya Pahuja 2026-04-10 13:57:37 +05:30
parent 0d4fae3638
commit fac9c78ee0
63 changed files with 1362 additions and 1 deletions

0
Week-3/GeneralNotes.md Normal file
View File

24
Week-3/Task-1/r3f/.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@ -0,0 +1,10 @@
# React + Vite
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
Currently, two official plugins are available:
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs)
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/)
## React Compiler

View File

@ -0,0 +1,29 @@
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import { defineConfig, globalIgnores } from 'eslint/config'
export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{js,jsx}'],
extends: [
js.configs.recommended,
reactHooks.configs.flat.recommended,
reactRefresh.configs.vite,
],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
parserOptions: {
ecmaVersion: 'latest',
ecmaFeatures: { jsx: true },
sourceType: 'module',
},
},
rules: {
'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
},
},
])

View File

@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>r3f</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>

View File

@ -0,0 +1,31 @@
{
"name": "week3-task1-r3f",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"@react-three/drei": "9.105.6",
"@react-three/fiber": "^9.5.0",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"three": "^0.183.2"
},
"packageManager": "yarn@1.22.22",
"devDependencies": {
"@eslint/js": "^9.39.4",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.1",
"eslint": "^9.39.4",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.5.2",
"globals": "^17.4.0",
"vite": "^8.0.1"
}
}

View File

@ -0,0 +1,41 @@
.app-container {
width: 100vw;
height: 100vh;
position: relative;
background-color: white;
overflow: hidden;
}
#controls {
position: absolute;
top: 20px;
left: 20px;
z-index: 10;
display: flex;
gap: 10px;
}
button {
padding: 8px 16px;
cursor: pointer;
background: #f0f0f0;
border: 1px solid #ccc;
border-radius: 4px;
font-weight: 500;
transition: all 0.2s;
}
button:hover {
background: #e0e0e0;
}
button.active {
background: #333;
color: white;
border-color: #333;
}
.canvas-wrapper {
width: 100%;
height: 100%;
}

View File

@ -0,0 +1,55 @@
import React, { useState } from 'react';
import { Canvas } from '@react-three/fiber';
import { OrbitControls, PerspectiveCamera } from '@react-three/drei';
import './App.css';
const materials = {
black: { color: "black", roughness: 1, metalness: 0 },
silver: { color: "#C0C0C0", roughness: 0.3, metalness: 1 },
gold: { color: "#FFD700", roughness: 0.4, metalness: 1 },
};
function Scene({ variant }) {
return (
<>
<color attach="background" args={["white"]} />
<ambientLight intensity={0.6 * Math.PI} />
<pointLight position={[5, 5, 5]} intensity={1.0 * Math.PI} />
<mesh rotation={[0, 0, 0]}>
<torusKnotGeometry args={[0.8, 0.3, 100, 16]} />
<meshStandardMaterial {...materials[variant]}/>
</mesh>
<OrbitControls makeDefault enableDamping dampingFactor={0.05} />
</>
);
}
export default function App() {
const [activeVariant, setActiveVariant] = useState('black');
return (
<div className="app-container">
<div id="controls">
{Object.keys(materials).map((variant) => (
<button
key={variant}
className={activeVariant === variant ? 'active' : ''}
onClick={() => setActiveVariant(variant)}
>
{variant.charAt(0).toUpperCase() + variant.slice(1)}
</button>
))}
</div>
<div className="canvas-wrapper">
<Canvas dpr={[1, 2]}>
<PerspectiveCamera makeDefault position={[0, 0, 5]} />
<Scene variant={activeVariant} />
</Canvas>
</div>
</div>
);
}

View File

@ -0,0 +1,8 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import App from './App.jsx'
createRoot(document.getElementById('root')).render(
<StrictMode>
<App />
</StrictMode>,
)

View File

@ -0,0 +1,7 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vite.dev/config/
export default defineConfig({
plugins: [react()],
})

1
Week-3/Task-1/report.md Normal file
View File

@ -0,0 +1 @@
# Task 1 Report

View File

@ -0,0 +1,22 @@
# Thob Builder Notes: Task 1 - Material Variant Switcher
## Overview
Implemented the basic material configuration pattern using Thob's built-in `MaterialVariant` and `Material` properties.
## Visual Observations
- **Lighting Limitation**: I observed that by default, the light only hits the object from one side, leaving the other side completely dark.
- **Surface Definition**: Standard material properties (color, roughness, metalness) were easy to edit via the sidebar properties panel.
## Technical Observations (Console Logs)
During development in the builder, several warnings and errors were observed:
- **404 Errors**: `GET https://builder.thob.studio/builder/... 404 (Not Found)`.
- **Hydration Warnings**: `No HydrateFallback element provided to render during initial hydration`.
- **Component State**: `undefined is changing from uncontrolled to controlled`. This suggests a potential issue in how the builder manages internal component state when swapping materials.
## Builder Workflow
- **Node Structure**: The scene was built using a `Canvas` -> `mesh` -> `sphereGeometry` hierarchy.
- **Variant Linking**: A `MaterialVariant` node was used to manage the switching logic.
- **UI Integration**: A simple `Button` was added under the `UI` group to trigger the material change.
## Conclusion
The builder makes it very easy to define and link material variants without writing code, but the default lighting environment needs more control (e.g., adding more lights or an environment map) to avoid the "dark side" effect.

View File

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Material Variant Switcher</title>
</head>
<body>
<div id="controls">
<button data-variant="black" class="active">Black</button>
<button data-variant="silver">Silver</button>
<button data-variant="gold">Gold</button>
</div>
<script type="module" src="./main.js"></script>
</body>
</html>

View File

@ -0,0 +1,80 @@
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
const scene = new THREE.Scene();
scene.background = new THREE.Color("white");
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.z = 5;
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const geometry = new THREE.TorusKnotGeometry(1.5, 0.5, 100, 16);
const materials = {
black: new THREE.MeshStandardMaterial({
color: "black",
roughness: 1,
metalness: 0,
}),
silver: new THREE.MeshStandardMaterial({
color: "#C0C0C0",
roughness: 0.3,
metalness: 1,
}),
gold: new THREE.MeshStandardMaterial({
color: "#FFD700",
roughness: 0.4,
metalness: 1,
}),
};
const sphere = new THREE.Mesh(geometry, materials.black);
scene.add(sphere);
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
scene.add(ambientLight);
const pointLight = new THREE.PointLight(0xffffff, 1);
pointLight.position.set(5, 5, 5);
scene.add(pointLight);
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
const buttons = document.querySelectorAll("button");
buttons.forEach((btn) => {
btn.addEventListener("click", () => {
const variant = btn.getAttribute("data-variant");
sphere.material = materials[variant];
buttons.forEach((b) => b.classList.remove("active"));
btn.classList.add("active");
});
});
function animate() {
requestAnimationFrame(animate);
controls.update();
sphere.rotation.y += 0.01;
renderer.render(scene, camera);
}
animate();
window.addEventListener("resize", () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});

View File

@ -0,0 +1,18 @@
{
"name": "week3-task1-vanilla",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"packageManager": "yarn@1.22.22",
"devDependencies": {
"vite": "^8.0.1"
},
"dependencies": {
"three": "^0.183.2"
}
}

View File

24
Week-3/Task-2/r3f/.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@ -0,0 +1,10 @@
# React + Vite
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
Currently, two official plugins are available:
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs)
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/)
## React Compiler

View File

@ -0,0 +1,29 @@
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import { defineConfig, globalIgnores } from 'eslint/config'
export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{js,jsx}'],
extends: [
js.configs.recommended,
reactHooks.configs.flat.recommended,
reactRefresh.configs.vite,
],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
parserOptions: {
ecmaVersion: 'latest',
ecmaFeatures: { jsx: true },
sourceType: 'module',
},
},
rules: {
'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
},
},
])

View File

@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>r3f</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>

View File

@ -0,0 +1,31 @@
{
"name": "week3-task2-r3f",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"@react-three/drei": "9.105.6",
"@react-three/fiber": "^9.5.0",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"three": "^0.183.2"
},
"packageManager": "yarn@1.22.22",
"devDependencies": {
"@eslint/js": "^9.39.4",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.1",
"eslint": "^9.39.4",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.5.2",
"globals": "^17.4.0",
"vite": "^8.0.1"
}
}

View File

@ -0,0 +1,41 @@
.app-container {
width: 100vw;
height: 100vh;
position: relative;
background-color: white;
overflow: hidden;
}
#controls {
position: absolute;
top: 20px;
left: 20px;
z-index: 10;
display: flex;
gap: 10px;
}
button {
padding: 8px 16px;
cursor: pointer;
background: #f0f0f0;
border: 1px solid #ccc;
border-radius: 4px;
font-weight: 500;
transition: all 0.2s;
}
button:hover {
background: #e0e0e0;
}
button.active {
background: #333;
color: white;
border-color: #333;
}
.canvas-wrapper {
width: 100%;
height: 100%;
}

View File

@ -0,0 +1,53 @@
import React, { useState} from 'react';
import { Canvas } from '@react-three/fiber';
import { OrbitControls, PerspectiveCamera, useTexture } from '@react-three/drei';
import './App.css';
const planetUrls = {
earth: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQZVsp7bSmsdGhM1GouOYgZ6l06Za__Z1ZY8A&s',
moon: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcROh1go667NHsMdzLyvI-0tt9Mn0eugRp0xhQ&s',
sun: 'https://upload.wikimedia.org/wikipedia/commons/a/a4/Solarsystemscope_texture_8k_sun.jpg',
};
function Planet({ variant }) {
const textures = useTexture(planetUrls);
return (
<mesh rotation={[0, 0, 0]}>
<sphereGeometry args={[1.5, 100, 100]} />
<meshStandardMaterial map={textures[variant]} roughness={0.6} />
</mesh>
);
}
export default function App() {
const [activeVariant, setActiveVariant] = useState('earth');
return (
<div className="app-container">
<div id="controls">
{Object.keys(planetUrls).map((variant) => (
<button
key={variant}
className={activeVariant === variant ? 'active' : ''}
onClick={() => setActiveVariant(variant)}
>
{variant.charAt(0).toUpperCase() + variant.slice(1)}
</button>
))}
</div>
<div className="canvas-wrapper">
<Canvas dpr={[1, 2]}>
<color attach="background" args={["white"]} />
<PerspectiveCamera makeDefault position={[0, 0, 5]} />
<ambientLight intensity={0.6 * Math.PI} />
<pointLight position={[5, 5, 5]} intensity={1.0 * Math.PI} />
<Planet variant={activeVariant} />
<OrbitControls makeDefault enableDamping dampingFactor={0.05} />
</Canvas>
</div>
</div>
);
}

View File

@ -0,0 +1,12 @@
* {
box-sizing: border-box;
}
body, html, #root {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow: hidden;
background-color: #111;
}

View File

@ -0,0 +1,10 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.jsx'
createRoot(document.getElementById('root')).render(
<StrictMode>
<App />
</StrictMode>,
)

View File

@ -0,0 +1,7 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vite.dev/config/
export default defineConfig({
plugins: [react()],
})

1
Week-3/Task-2/report.md Normal file
View File

@ -0,0 +1 @@
# Task 2 Report

View File

@ -0,0 +1,22 @@
# Thob Builder Notes: Task 2 - Texture Switcher
## Overview
Implemented a planetary texture switcher (Earth/Moon) using a `RadioGroup` for the UI and the built-in `MaterialVariant` system for texture swapping.
## Visual Observations
- **Texture Resolution**: The builder handles texture mapping on a sphere smoothly.
- **UI Interaction**: Using a `RadioGroup` feels more natural for a configurator pattern compared to a single toggle button.
## Technical Observations (Console Logs)
During development in the builder, several warnings and errors were observed:
- **404 Errors**: `GET https://builder.thob.studio/builder/... 404 (Not Found)`.
- **Method Registration**: `GetBindingData... method already registered`. This appears repeatedly in the logs during the preview phase.
- **Hydration Warnings**: `No HydrateFallback element provided to render during initial hydration`.
- **Component State**: `undefined is changing from uncontrolled to controlled`. This suggests a potential issue in how the builder manages internal component state when switching variants via `RadioGroup`.
## Builder Workflow
- **Node Hierarchy**: The UI was structured using a `RadioGroup` -> `For` loop -> `RadioGroupItem` layout, demonstrating high-level UI component support in the builder.
- **State Linking**: Connecting the `RadioGroup` selection to the `MaterialVariant` property was straightforward in the properties panel.
## Conclusion
The Thob Builder's `RadioGroup` component is a strong candidate for professional configurators. The process of linking textures to UI elements is intuitive and requires zero code. However, the runtime console warnings should be investigated to ensure production stability.

View File

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Task 2 — Texture / Surface Variant Switcher</title>
</head>
<body>
<div id="controls">
<button data-variant="earth" class="active">Earth</button>
<button data-variant="moon">Moon</button>
<button data-variant="sun">Sun</button>
</div>
<script type="module" src="./main.js"></script>
</body>
</html>

View File

@ -0,0 +1,75 @@
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
const scene = new THREE.Scene();
scene.background = new THREE.Color("white");
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.z = 5;
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const geometry = new THREE.SphereGeometry(1.5, 100, 100);
const loader = new THREE.TextureLoader();
const textures = {
earth: loader.load('https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQZVsp7bSmsdGhM1GouOYgZ6l06Za__Z1ZY8A&s'),
moon: loader.load('https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcROh1go667NHsMdzLyvI-0tt9Mn0eugRp0xhQ&s'),
sun: loader.load('https://upload.wikimedia.org/wikipedia/commons/a/a4/Solarsystemscope_texture_8k_sun.jpg')
};
const material = new THREE.MeshStandardMaterial({
map: textures.earth,
roughness: 0.6
});
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
scene.add(ambientLight);
const pointLight = new THREE.PointLight(0xffffff, 1);
pointLight.position.set(5, 5, 5);
scene.add(pointLight);
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
const buttons = document.querySelectorAll("button");
buttons.forEach((btn) => {
btn.addEventListener("click", () => {
const variant = btn.getAttribute("data-variant");
mesh.material.map = textures[variant];
mesh.material.needsUpdate = true;
buttons.forEach((b) => b.classList.remove("active"));
btn.classList.add("active");
});
});
function animate() {
requestAnimationFrame(animate);
controls.update();
mesh.rotation.y += 0.01;
renderer.render(scene, camera);
}
animate();
window.addEventListener("resize", () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});

View File

@ -0,0 +1,18 @@
{
"name": "week3-task2-vanilla",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"packageManager": "yarn@1.22.22",
"devDependencies": {
"vite": "^8.0.1"
},
"dependencies": {
"three": "^0.183.2"
}
}

24
Week-3/Task-3/r3f/.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@ -0,0 +1,10 @@
# React + Vite
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
Currently, two official plugins are available:
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs)
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/)
## React Compiler

View File

@ -0,0 +1,29 @@
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import { defineConfig, globalIgnores } from 'eslint/config'
export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{js,jsx}'],
extends: [
js.configs.recommended,
reactHooks.configs.flat.recommended,
reactRefresh.configs.vite,
],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
parserOptions: {
ecmaVersion: 'latest',
ecmaFeatures: { jsx: true },
sourceType: 'module',
},
},
rules: {
'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
},
},
])

View File

@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>r3f</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>

View File

@ -0,0 +1,31 @@
{
"name": "week3-task3-r3f",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"@react-three/drei": "9.105.6",
"@react-three/fiber": "^9.5.0",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"three": "^0.183.2"
},
"packageManager": "yarn@1.22.22",
"devDependencies": {
"@eslint/js": "^9.39.4",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.1",
"eslint": "^9.39.4",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.5.2",
"globals": "^17.4.0",
"vite": "^8.0.1"
}
}

View File

@ -0,0 +1,12 @@
.app-container {
width: 100vw;
height: 100vh;
position: relative;
background-color: white;
overflow: hidden;
}
.canvas-wrapper {
width: 100%;
height: 100%;
}

View File

@ -0,0 +1,64 @@
import React, { useState } from 'react';
import { Canvas } from '@react-three/fiber';
import { OrbitControls, PerspectiveCamera } from '@react-three/drei';
import './App.css';
const variants = {
black: {
geometry: <boxGeometry args={[2, 2, 2]} />,
material: { color: "black", roughness: 1, metalness: 0 }
},
silver: {
geometry: <sphereGeometry args={[1.5, 32, 32]} />,
material: { color: "#C0C0C0", roughness: 0.3, metalness: 1 }
},
gold: {
geometry: <torusGeometry args={[1.2, 0.4, 16, 100]} />,
material: { color: "#FFD700", roughness: 0.4, metalness: 1 }
}
};
function Product({ variant }) {
const { geometry, material } = variants[variant];
return (
<mesh>
{geometry}
<meshStandardMaterial {...material} />
</mesh>
);
}
export default function App() {
const [activeVariant, setActiveVariant] = useState('black');
return (
<div className="app-container">
<div id="controls">
{Object.keys(variants).map((variant) => (
<button
key={variant}
className={activeVariant === variant ? 'active' : ''}
onClick={() => setActiveVariant(variant)}
>
{variant.charAt(0).toUpperCase() + variant.slice(1)}
</button>
))}
</div>
<div className="canvas-wrapper">
<Canvas dpr={[1, 2]}>
<color attach="background" args={["white"]} />
<PerspectiveCamera makeDefault position={[0, 0, 5]} />
<ambientLight intensity={0.6 * Math.PI} />
<pointLight position={[5, 5, 5]} intensity={1.0 * Math.PI} />
<Product variant={activeVariant} />
<OrbitControls makeDefault enableDamping dampingFactor={0.05} />
</Canvas>
</div>
</div>
);
}

View File

@ -0,0 +1,12 @@
* {
box-sizing: border-box;
}
body, html, #root {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow: hidden;
background-color: #111;
}

View File

@ -0,0 +1,10 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.jsx'
createRoot(document.getElementById('root')).render(
<StrictMode>
<App />
</StrictMode>,
)

View File

@ -0,0 +1,7 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vite.dev/config/
export default defineConfig({
plugins: [react()],
})

1
Week-3/Task-3/report.md Normal file
View File

@ -0,0 +1 @@
# Task 3 Report

View File

View File

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Task 3 — UI-Controlled Product Option</title>
<link rel="stylesheet" href="./style.css">
</head>
<body>
<div id="controls">
<button data-variant="black" class="active">Black</button>
<button data-variant="silver">Silver</button>
<button data-variant="gold">Gold</button>
</div>
<script type="module" src="./main.js"></script>
</body>
</html>

View File

@ -0,0 +1,92 @@
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
const scene = new THREE.Scene();
scene.background = new THREE.Color("white");
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.z = 5;
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const geometries = {
black: new THREE.BoxGeometry(2, 2, 2),
silver: new THREE.SphereGeometry(1.5, 32, 32),
gold: new THREE.TorusGeometry(1.2, 0.4, 16, 100),
};
const materials = {
black: new THREE.MeshStandardMaterial({
color: "black",
roughness: 1,
metalness: 0,
}),
silver: new THREE.MeshStandardMaterial({
color: "#C0C0C0",
roughness: 0.3,
metalness: 1,
}),
gold: new THREE.MeshStandardMaterial({
color: "#FFD700",
roughness: 0.4,
metalness: 1,
}),
};
let currentVariant = "black";
let mesh = new THREE.Mesh(
geometries[currentVariant],
materials[currentVariant]
);
scene.add(mesh);
scene.add(new THREE.AmbientLight(0xffffff, 0.6));
const pointLight = new THREE.PointLight(0xffffff, 1);
pointLight.position.set(5, 5, 5);
scene.add(pointLight);
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
function updateScene() {
mesh.geometry = geometries[currentVariant];
mesh.material = materials[currentVariant];
}
const buttons = document.querySelectorAll("button");
buttons.forEach((btn) => {
btn.addEventListener("click", () => {
const variant = btn.getAttribute("data-variant");
currentVariant = variant;
updateScene();
buttons.forEach((b) => b.classList.remove("active"));
btn.classList.add("active");
});
});
function animate() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
}
animate();
window.addEventListener("resize", () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});

View File

@ -0,0 +1,18 @@
{
"name": "week3-task3-vanilla",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"packageManager": "yarn@1.22.22",
"devDependencies": {
"vite": "^8.0.1"
},
"dependencies": {
"three": "^0.183.2"
}
}

24
Week-3/Task-4/r3f/.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@ -0,0 +1,10 @@
# React + Vite
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
Currently, two official plugins are available:
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs)
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/)
## React Compiler

View File

@ -0,0 +1,29 @@
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import { defineConfig, globalIgnores } from 'eslint/config'
export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{js,jsx}'],
extends: [
js.configs.recommended,
reactHooks.configs.flat.recommended,
reactRefresh.configs.vite,
],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
parserOptions: {
ecmaVersion: 'latest',
ecmaFeatures: { jsx: true },
sourceType: 'module',
},
},
rules: {
'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
},
},
])

View File

@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>r3f</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>

View File

@ -0,0 +1,31 @@
{
"name": "week3-task4-r3f",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"@react-three/drei": "9.105.6",
"@react-three/fiber": "^9.5.0",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"three": "^0.183.2"
},
"packageManager": "yarn@1.22.22",
"devDependencies": {
"@eslint/js": "^9.39.4",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.1",
"eslint": "^9.39.4",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.5.2",
"globals": "^17.4.0",
"vite": "^8.0.1"
}
}

View File

@ -0,0 +1,12 @@
.app-container {
width: 100vw;
height: 100vh;
position: relative;
background-color: white;
overflow: hidden;
}
.canvas-wrapper {
width: 100%;
height: 100%;
}

View File

@ -0,0 +1,48 @@
import React, { useState } from 'react';
import { Canvas } from '@react-three/fiber';
import { OrbitControls, PerspectiveCamera } from '@react-three/drei';
import './App.css';
function InteractiveObject() {
const [selected, setSelected] = useState(false);
return (
<mesh
onClick={() => setSelected(!selected)}
scale={selected ? 1.2 : 1}
rotation={[0, 0, 0]}
>
<torusKnotGeometry args={[1, 0.4, 100, 16]} />
<meshStandardMaterial
color={selected ? "#3498db" : "#ccc"}
emissive={selected ? "#1e3799" : "#000"}
emissiveIntensity={selected ? 0.5 : 0}
roughness={0.5}
/>
</mesh>
);
}
export default function App() {
return (
<div className="app-container">
<div id="overlay">
<h1>Interaction Pattern Click on the object</h1>
</div>
<div className="canvas-wrapper">
<Canvas dpr={[1, 2]}>
<color attach="background" args={["white"]} />
<PerspectiveCamera makeDefault position={[0, 0, 5]} />
<ambientLight intensity={0.6 * Math.PI} />
<pointLight position={[5, 5, 5]} intensity={1.0 * Math.PI} />
<InteractiveObject />
<OrbitControls makeDefault enableDamping dampingFactor={0.05} />
</Canvas>
</div>
</div>
);
}

View File

@ -0,0 +1,12 @@
* {
box-sizing: border-box;
}
body, html, #root {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow: hidden;
background-color: #111;
}

View File

@ -0,0 +1,10 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.jsx'
createRoot(document.getElementById('root')).render(
<StrictMode>
<App />
</StrictMode>,
)

View File

@ -0,0 +1,7 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vite.dev/config/
export default defineConfig({
plugins: [react()],
})

1
Week-3/Task-4/report.md Normal file
View File

@ -0,0 +1 @@
# Task 4 Report

View File

View File

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Task 4 — Click To Highlight / Select</title>
<style>
body { margin: 0; padding: 0; overflow: hidden; background: white; font-family: sans-serif; }
</style>
</head>
<body>
<h1>Interaction Pattern- click on the object</h1>
<script type="module" src="./main.js"></script>
</body>
</html>

View File

@ -0,0 +1,70 @@
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
const scene = new THREE.Scene();
scene.background = new THREE.Color("white");
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 5;
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const geometry = new THREE.TorusKnotGeometry(1, 0.4, 100, 16);
const material = new THREE.MeshStandardMaterial({ color: "#ccc", roughness: 0.5 });
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
scene.add(new THREE.AmbientLight(0xffffff, 0.6));
const pointLight = new THREE.PointLight(0xffffff, 1);
pointLight.position.set(5, 5, 5);
scene.add(pointLight);
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
let isSelected = false;
window.addEventListener("click", (event) => {
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObject(mesh);
if (intersects.length > 0) {
isSelected = !isSelected;
updateAppearance();
}
});
function updateAppearance() {
if (isSelected) {
mesh.scale.set(1.2, 1.2, 1.2);
mesh.material.color.set("#3498db");
mesh.material.emissive.set("#1e3799");
mesh.material.emissiveIntensity = 0.5;
} else {
mesh.scale.set(1, 1, 1);
mesh.material.color.set("#ccc");
mesh.material.emissive.set("#000");
mesh.material.emissiveIntensity = 0;
}
}
function animate() {
requestAnimationFrame(animate);
controls.update();
mesh.rotation.y += 0.01;
renderer.render(scene, camera);
}
animate();
window.addEventListener("resize", () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});

View File

@ -0,0 +1,18 @@
{
"name": "week3-task4-vanilla",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"packageManager": "yarn@1.22.22",
"devDependencies": {
"vite": "^8.0.1"
},
"dependencies": {
"three": "^0.183.2"
}
}

View File

@ -0,0 +1 @@
# Week 3 Personal Summary

View File

@ -19,7 +19,8 @@
"packageManager": "yarn@1.22.22", "packageManager": "yarn@1.22.22",
"workspaces": [ "workspaces": [
"Week-1/**", "Week-1/**",
"Week-2/**" "Week-2/**",
"Week-3/**"
], ],
"dependencies": { "dependencies": {
"@react-three/fiber": "^9.5.0", "@react-three/fiber": "^9.5.0",